6
votes

If you imagine two models defined thus:

App.User = DS.Model.extend({
    emails: DS.hasMany('email', {embedded: 'always'}),
});

App.Email = DS.Model.extend({
    address: DS.attr('string'),
    alias: DS.attr('string'),
    user: DS.belongsTo('user')
});

... and a REST Adapter:

App.UserAdapter = DS.RESTAdapter.extend({
    url: 'http://whatever.com',
    namespace: 'api/v1'
});

... with routing set up like so:

App.Router.map(function () {
    this.route('index', { path: '/' });
    this.resource('users', function () {
        this.route('index');
        this.route('add');
        this.resource('user', { path: ':user_id' }, function () {
            this.route('delete');
            this.route('edit');
            this.resource('emails', function () {
                this.route('index');
                this.route('add');
                this.resource('email', { path: ':email_id' }, function () {
                    this.route('delete');
                    this.route('edit');
                });
            });
        });
    });
});

... and a controller action to save the edited email, which looks like this:

App.EmailEditController = Ember.ObjectController.extend({
    actions: {
        save: function () {
            var self = this;
            var email = this.get('model');
            email.save().then(function(){
                self.transitionToRoute('email', email);
            });
        }
    }
});

The issue is this...

The PUT request is being sent to: http://whatever.com/api/v1/emails/[email_id]

However the correct API endpoint is: http://whatever.com/api/v1/users/[user_id]/emails/[email_id]

What is the correct way to remedy this issue?

2
Try adding user: DS.belongsTo('user') to the email model. Then the email knows which user it belongs to and the nested path can be built properly. - buuda

2 Answers

5
votes

The solution I came up with was just to rewrite createRecord, updateRecord and deleteRecord in the REST adapter.

I added a 'parent' attribute to the models affected. In the *Record hooks, I can check if this is set and edit the path sent to buildURL accordingly.

My createRecord, updateRecord and deleteRecord hooks now looks something similar to this:

App.UserAdapter = DS.RESTAdapter.extend({

    createRecord: function (store, type, record) {

        if (!record.get('parent') || null === record.get('parent')) {
            return this._super(store, type, record);
        }

        var data = {};
        var serializer = store.serializerFor(type.typeKey);

        var parent_type = record.get('parent');
        var parent_id = record.get(parent_type).get('id');
        var child_type = Ember.String.camelize(
            Ember.String.pluralize(
                type.typeKey.split(
                    record.get('parent')
                ).pop()
            )
        );

        var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;

        serializer.serializeIntoHash(data, type, record, { includeId: true });

        return this.ajax(this.buildURL(path), "POST", { data: data });
    },

    updateRecord: function(store, type, record) {

        if(!record.get('parent') || null === record.get('parent')){
            return this._super(store, type, record);
        }

        var data = {};
        var serializer = store.serializerFor(type.typeKey);

        var parent_type = record.get('parent');
        var parent_id = record.get(parent_type).get('id');
        var child_type = Ember.String.camelize(
            Ember.String.pluralize(
                type.typeKey.split(
                    record.get('parent')
                ).pop()
            )
        );

        var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;

        serializer.serializeIntoHash(data, type, record);
        var id = record.get('id');

        return this.ajax(this.buildURL(path, id), "PUT", { data: data });
    },

    deleteRecord: function (store, type, record) {

        if (!record.get('parent')) {
            return this._super(store, type, record);
        }

        var parent_type = record.get('parent');
        var parent_id = record.get('parent_id');
        var child_type = Ember.String.camelize(
            Ember.String.pluralize(
                type.typeKey.split(
                    record.get('parent')
                ).pop()
            )
        );

        var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;
        var id = record.get('id');

        return this.ajax(this.buildURL(path, id), "DELETE");
    }

});

The Email model in the example would be something like:

App.Email = DS.Model.extend({
    address: DS.attr('string'),
    alias: DS.attr('string'),
    user: DS.belongsTo('user'),
    parent: 'user'
});
4
votes

I solved this by overriding the buildURL method in model-specific adapters when required, using a mixin to encapsulate the method. Basically, it uses the default method to get the URL built according to Ember's rules and then it slices and puts additional info in place. Of course, this works because in buildURL we have access to the record...

Here is the basic idea in CoffeeScript:

module.exports = App.RestWithParentMixin = Ember.Mixin.create
  host: App.Environment.get('hostREST')
  namespace: App.Environment.get('apiNamespace')
  ancestorTypes: null

  buildURL: (type, id, record) ->
    url = @_super(type, id, record)
    ancestorTypes = @get('ancestorTypes')
    if ancestorTypes == null
        urlFixed = url
    else
        urlPrefix = @urlPrefix()
        urlWithoutPrefix = url.slice(urlPrefix.length)
        ancestry = []
        ancestorTypes
        if not Array.isArray(ancestorTypes)
            ancestorTypes = [ancestorTypes]
        for ancestorType in ancestorTypes
            ancestor = record.get(ancestorType)
            ancestorID = ancestor.get('id')
            ancestry.push(ancestorType)
            ancestry.push(ancestorID)
        urlFixed = urlPrefix + '/' + ancestry.join('/') + urlWithoutPrefix
    urlFixed

PS: A small edit to add that I this was made using Ember 1.7.1 and Ember Data 1.0.0-beta.11