RESTAdapter: loading hasMany relationships with nested path


#1

Hi all.

I am having a hard time finding answers or resources explaining how to do this.

I have a model like so:

Library = DS.Model.extend
  name: DS.attr 'string'
  groups: DS.hasMany 'group', {async: true}
  groupsCount: DS.attr 'number'

I want to load the groups for a given library.

The API does not support returning sideloaded relationships. Instead, it works by providing a nested path to load relationships, like: /libraries/:id/groups.

How can I change the RESTAdapter to get this working?


#2

Does the nested path come back in the library response? Can you post an example of the response?


#3

Here’s the response to GET /library/:id:

{
  library: {
    groups_count: 44,
    name: "demo",
    id: "545262a063726d2514390100"
  }
}

#4

If you have control of the API, you can add a links param like:

{
  library: {
    groups_count: 44,
    name: "demo",
    id: "545262a063726d2514390100"
    links: {
      "groups": "library/545262a063726d2514390100/groups"
  }
}

then you can use the built in functionality of findHasMany to get the nested resource in your route like this:

model: function(params){
  this.store.find('library', params.id);
}
setupController: function(controller, model){
  @_super(controller, model)
  controller.set('model', model)
  model.get('groups');
}

But it sounds like you do not have control over the api, so you might have to do some hacking on the findquery of certain adapters, something like:

this.store.find('group', { type: 'library', library:{library_id} });


App.GroupAdapter = DS.RESTAdapter.extend({
  findQuery: function(store, type, params, record){
    if (params.type == 'library')
      return this.ajax(“library/#{params.library}/groups”, ‘GET’);
    else
       this._super(store, type, params, record);
  }
});

#5

I see, thank you for your answer. Indeed I do not have control over the API. Could I do something in the serializer where I automatically add a ‘link’ property to my payload to fake that the server responded with it? This way I can use Ember Data in the way it is intended (model.get('groups') not this.store.find('group', { type: 'library', library:{library_id} })).

If it is possible, can you show me how? I am still not sure how serializers tie in with everything else.


#6

I’m pretty sure you can do that, but I also don’t know much about the serializer. I saw another thread where someone mentioned hacking the link property into the serializer. I would try something like this first:

App.LibrarySerializer = DS.RESTSerializer.extend({
  extractSingle: function(store, type, payload, id) {
    payload.library.links = {"groups": "libary/"+ id +"}/groups"};
    return this._super(store, type, payload, id);
  }
});

#7

Thanks for the help Chris. After some research I found a way that works for me:

LibrarySerializer = DS.RESTSerializer.extend
  normalize: (type, hash, prop)->
    hash.links =
      groups: "groups"
    @_super(type, hash, prop)

That did the trick. Now I wish there was a way to do something like this globally for an adapter, not model per model (since the entire API is built like that). I know there’s the ApplicationAdapter but this is not usable for me since I have more than 1 backend I am integrating against.


#8

Cool, glad you got it working.