Sending REST requests to a nested API endpoint URL using Ember Data

ember-data, ember.js

Solution

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'
});

Problem

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?

Original source