Ember Data is passing a query param instead of accessing an API endpoint. Can you help?


#1

Background 01

Background 02

I’m writing a simple app which has categories, and items contained within those categories.

App.Router.map(function() {
    this.resource('index', { path: '/'});
    this.resource('category', { path: '/:category_id'});
});

App.ApplicationRoute = Ember.Route.extend({
    model: function(params) {
        return this.store.find('category');
    }
});

App.CategoryRoute = Ember.Route.extend({
    model: function (params) {
        return Ember.RSVP.hash({
            category: this.store.find('category', params.category_id),
            items: this.store.find('item', {category: params.category_id})
        });
    }
});

App.Category = DS.Model.extend({
    name: DS.attr('string'),
    description: DS.attr('string'),
    items: DS.hasMany('item', {async: true}),
});

App.Item = DS.Model.extend({
    category: DS.belongsTo('category'),
    name: DS.attr('string'),
    description: DS.attr('string'),
    url: DS.attr('string'), // typically a demo URL. NOT the repo URL.
    repository: DS.attr('string')
});

When a category loads I retrieve a list of items from the server. This is currently working, but for some reason is passing a query string instead of hitting an API endpoint directly. I would prefer this /api/items/videos but I’m getting this /api/items?category=videos. I’ve got the relevant bits of my code posted above. I’ve changed this line this.store.find('item' ...), passing just the category_id raw (instead of in an object). While that does work to call the endpoint as I desire, it throws an error:

Assertion Failed: The value that #each loops over must be an Array. You passed '<App.Item:ember410:1>' (wrapped in (generated _items controller))

The data being returned from the server is identical in both cases:

{
  "items": [
    {
      "url": "http://cnn.com",
      "description": "An example of a video link",
      "id": 1,
      "name": "CNN Video"
    }
  ]
}

What am I doing wrong?


#2

I think you’ll need to create your category controller as an ArrayController.

App.CategoryController = Ember.ArrayController.extend({});

#3

If you are also writing the server side (or want to do some work in a serializer client side) you can set up an Ember Data relationship and return links in the parent record. Something like this:

App.CategoryRoute = Ember.Route.extend({
    model: function (params) {
        // only make a request if the user reloads. Model from link-to otherwise
        return this.store.find('category', params.category_id);
    }
});

// category.hbs
Category: {{name}}
{{#each item in items}}
    {{item.name}} / {{item.url}}
{{else}}
    Sorry, no items.
{{/each}}

App.Category = DS.Model.extend({
    name: DS.attr('string'),
    description: DS.attr('string'),
    items: DS.hasMany('item', {async: true}),
});

App.Item = DS.Model.extend({
    category: DS.belongsTo('category'),
    name: DS.attr('string'),
    description: DS.attr('string'),
    url: DS.attr('string'), // typically a demo URL. NOT the repo URL.
    repository: DS.attr('string')
});

{
  "category": {
    "name": "videos",
    "description": "A video category",
    "id": 1,
    "items": [1, 2, 3, 4],
    "links": {
      "items": "http://example.com/api/categories/1/items"
    }
  }
}

This may not be useful in your case but it took me a while to learn it myself (working with a nested api) so thought I’d post the pattern just in case.