1

I'm trying to find a recommended way to configure Ember Data to consume data from server that is using nested URLs. So far I tried following approaches:

Overriding buildURL

https://stackoverflow.com/a/30458763/775359

App.SubcolorAdapter = DS.RESTAdapter.extend({
  buildURL: function(type, id, snapshot, requestType, query) {
    var urlParts = location.hash.split("/"); // don't like the way how I'm accessing it
    var parentId = urlParts[2];
    var url = "/colors/" + parentId + "/subcolors";
    return url;
  }
});

Overriding findAll

https://stackoverflow.com/a/24411550/775359

App.SubcolorAdapter = DS.RESTAdapter.extend({
  findAll: function(store, type, sinceToken) {
    var urlParts = location.hash.split("/"); // don't like the way how I'm accessing it
    var parentId = urlParts[2];
    var url = "/colors/" + parentId + "/subcolors";
    return new Ember.RSVP.Promise(function(resolve, reject) {
      jQuery.getJSON(url).then(function(data) {
        Ember.run(null, resolve, data);
      }, function(jqXHR) {
        jqXHR.then = null;
        Ember.run(null, reject, jqXHR);
      });
    });
  }
});

It sort of works, but I don't like the way how I'm accessing the ID. I tried accessing arguments or this.get( ... ) in buildURL or findAll but no success.


Specifying model relationship

Ember Data nested resource URL - so I tried:

App.Color = DS.Model.extend({
  name: DS.attr('string'),
  subcolors: DS.hasMany('App.Subcolor'),
});   

App.Subcolor = DS.Model.extend({
  name: DS.attr('string'),
  color: DS.belongsTo('App.Color'),
});  

App.Router.map(function() {
  this.resource('color', function(){
    this.route('show', { path: ':color_id' }, function() {
      this.route('subcolor');
    });
  });
});

But the request is made to /subcolors that fails.




Ember.ENV.ENABLE_DS_FILTER = true;

    App = Ember.Application.create();

    App.ApplicationAdapter = DS.RESTAdapter;
    
    App.Router.map(function() {
      this.resource('color', function(){
        this.route('show', { path: ':color_id' }, function() {
          this.route('subcolor');
        });
      });
    });

    App.Color = DS.Model.extend({
      name: DS.attr('string'),
      subcolors: DS.hasMany('App.Subcolor'),
    });   

    App.Subcolor = DS.Model.extend({
      name: DS.attr('string'),
      color: DS.belongsTo('App.Color'),
    });  

    App.IndexRoute = Ember.Route.extend({    
      model: function() {
        return this.store.find('color');
      }
    });         

    App.ColorShowSubcolorRoute = Ember.Route.extend({    
      model: function() {
        return this.store.find('subcolor');
      }
    });

    // APPROACH 1
    //  
    //
    //
    // http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_buildURL
    // As suggested here: https://stackoverflow.com/a/30458763/775359

    // App.SubcolorAdapter = DS.RESTAdapter.extend({
    //   buildURL: function(type, id, snapshot, requestType, query) {
    //     var urlParts = location.hash.split("/");
    //     var parentId = urlParts[2];
    //     var url = "/colors/" + parentId + "/subcolors";
    //     return url;
    //   }
    // });


    // APPROACH 2
    //  
    //
    //
    // http://emberjs.com/api/data/classes/DS.Adapter.html#method_findAll
    // As suggested here: https://stackoverflow.com/a/24411550/775359

    // App.SubcolorAdapter = DS.RESTAdapter.extend({
    //   findAll: function(store, type, sinceToken) {
    //     var urlParts = location.hash.split("/");
    //     var parentId = urlParts[2];
    //     var url = "/colors/" + parentId + "/subcolors";
    //     return new Ember.RSVP.Promise(function(resolve, reject) {
    //       jQuery.getJSON(url).then(function(data) {
    //         Ember.run(null, resolve, data);
    //       }, function(jqXHR) {
    //         jqXHR.then = null;
    //         Ember.run(null, reject, jqXHR);
    //       });
    //     });
    //   }
    // });


    { // mocking AJAX requests (dummy braces so I can collapse in the editor)
      $.mockjax({
        type: 'GET',
        url: '/colors',
        dataType: 'json',
        responseText: {
          "colors" : [
            { id: 1, name: "Red" },
            { id: 2, name: "Blue" },
          ]
        }
      });      

      $.mockjax({
        type: 'GET',
        url: '/colors/1',
        dataType: 'json',
        responseText: {
          "colors" : [
            { id: 1, name: "Red" }
          ]
        }
      }); 

      $.mockjax({
        type: 'GET',
        url: '/colors/2',
        dataType: 'json',
        responseText: {
          "colors" : [
            { id: 2, name: "Blue" }
          ]
        }
      });       

      $.mockjax({
        type: 'GET',
        url: '/colors/1/subcolors',
        dataType: 'json',
        responseText: {
          "subcolors": [
            { id: 1, name: "Light Red" },
            { id: 2, name: "Dark Red"  }
          ]}
      });    

      $.mockjax({
        type: 'GET',
        url: '/colors/2/subcolors',
        dataType: 'json',
        responseText: {
          "subcolors": [
            { id: 3, name: "Light Blue"},
            { id: 4, name: "Dark Blue" }
          ]}
      });
    }
  <script type="text/x-handlebars" id="index">
    <h3>index</h3>
    <ul>
      {{#each color in model}}
        <li>{{#link-to "color.show" color}} {{color.name}} {{/link-to}}</li>
      {{/each}}
    </ul>
   </script>

  <script type="text/x-handlebars" id="color/show">
    <h3>color/show</h3>
    {{#link-to "application"}}Go back to the list{{/link-to}}
    <br>
    {{ model.name }}
    <br>
    {{#link-to "color.show.subcolor" model}} go to subcolors {{/link-to}}

    {{outlet}}
  </script>  

  <script type="text/x-handlebars" id="color/show/subcolor">
    <h3>color/show/subcolor</h3>
    <ul>
      {{#each subcolor in model}}
        <li>{{subcolor.name}}</li>
      {{/each}}
    </ul>
  </script>
 
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.5.3/jquery.mockjax.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.13.2/ember.debug.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.13.2/ember-template-compiler.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.13.3/ember-data.js"></script> 



For demo purposes (so it is runnable and self-contained) I mock the following GET requests:

/colors
/colors/1
/colors/1/subcolors
/colors/2
/colors/2/subcolors
Community
  • 1
  • 1
Mars Robertson
  • 12,673
  • 11
  • 68
  • 89
  • what exactly do you mean by "nested URLs"? Can you show the JSON response from your server? Normally you can nest `route` calls in your router and you shouldn't have to override `buildUrl` and `findAll` – Steve H. Jun 23 '15 at 20:49
  • Response from the server - *Show code snippet* - you'll see some sample data I'm mocking... (there are 5 requests in total) My actual API is a little bit rigid at this point so I'd rather configure Ember Data. – Mars Robertson Jun 23 '15 at 20:51
  • Having similar issue so far my theoretical approach is to have a service and adapter for the top level resource so for your example `ColorAdapter` and `ColorService` when navigating to `/colors/1` in one of your route hooks set `ColorService.colorId` to `1` and in your model hooks for `/colors/1/subcolors` call a method like `ColorService.getSubcolors` which calls the adapter `ColorAdapter.fetchSubColors` – BillPull Jun 24 '15 at 15:06
  • Hi @MichalStefanow did you find a good way to get nested resources using ED? – hernanvicente Apr 18 '16 at 11:18
  • Hi @hernanvicente - that was a long time ago and I cannot remember how it was solved. If I had an easy straightforward answer back then I'd answer the question myself... – Mars Robertson Apr 18 '16 at 11:32

0 Answers0