14

How are singular resources handled in ember-data? Say I have the following RESTful routes:

GET /cart
POST /cart
UPDATE /cart
DELETE /cart

ember-data expects find() to return an array, plus it automatically tries to pluralize any url I pass to my model. What is the best way to handle this situation?

Nick Colgan
  • 5,488
  • 25
  • 36

4 Answers4

7

There are a number of things you can do here.

The RESTAdapter calls pluralize, which either adds an "s" to the end of the name, or looks up the name in the plurals hash if it exists. Assuming your DS.Model is App.Cart.

https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L209

DS.RESTAdapter.create({
  plurals: {
    cart: 'cart'
  }
});

If your URL scheme is very different and requires some further logic, you can actually override the buildURL function.

https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L288

DS.RestAdapter.create({
  buildURL: function() {
    return "/always_this"
  })
});
Community
  • 1
  • 1
Ryan
  • 3,594
  • 1
  • 24
  • 23
  • Overriding the buildURL function will apply to all models, though, right? What about my non-singular models? Also, even if I change the plurals, `find()` (without arguments) expects an array of objects, so that won't work. And if I pass a fake id to find, then I have to modify my server routes to accept an id argument (`/cart/:fake_id`) only to disregard it completely. It just seems too hacky for something so common and simple. – Nick Colgan Sep 22 '12 at 01:29
  • Build URL can do something like this: ``if (record.get('singularResouce') { ... } else { this._super.apply(this, arguments); }``. The (...) can be your own singular building URL function, and your singular models will have ``DS.Model.extend({ singularResouce: true })``. – Ryan Sep 22 '12 at 01:41
  • You can even do something snazzy like call ``buildUrl`` again, but pass in undefined as the id. – Ryan Sep 22 '12 at 01:46
  • Yeah, buildURL won't work for another reason: no actual objects are passed into it, just a string. record == 'cart'. So I can't check it for any properties (`get('singularResource')`) – Nick Colgan Sep 22 '12 at 02:18
  • ``App.Cart.reopenClass({ singularRecourse: true });`` and then ``App.get(record + '.singularRecourse');`` You get the idea. Play with it, it will work. Let us know what you find. – Ryan Sep 22 '12 at 02:21
  • There is also rootForType's ``root.type.url``. While this won't solve your problem, it is worth noting to others that is is another way to get more naming control: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L214 – Ryan Sep 22 '12 at 02:26
  • Yeah. I'll play around with it for a while and post what I come up with. Thanks for the help. – Nick Colgan Sep 22 '12 at 02:36
  • 1
    With new version of Ember data this does no longer work. See also http://stackoverflow.com/a/21839261/2049986, this does still work. – Jacob van Lingen Jul 07 '14 at 11:28
3

So, I found this pull request on github. It's 8 months old, so won't work due to added complexity since then, but I've implemented the workaround suggested like so:

App.store = DS.Store.create({
  revision: 4,
  adapter: DS.RESTAdapter.create({
    plurals: {
      'cart': 'cart'
    }
  })
});

App.Cart.reopenClass({
  find: function () {
    this._super("singleton");
  }
});

On my server (I'm using rails), I have to add the following to my routes:

get "cart/:ignored" => "carts#show"

Then I have to add the following to CartSerializer (using active_model_serializers gem):

attributes :id
def id
  "singleton"
end

This is necessary, because, apparently, if the id in the json response doesn't match the id requested from find() (singleton in this case), then ember won't load the data into the model.

Now, this obviously isn't the ideal solution, but until ember-data adds support for it, it seems like the least painful way to go.

By the way, I filed an issue to add support.

Nick Colgan
  • 5,488
  • 25
  • 36
  • I tried implementing this solution and it works on initial load...but once I navigate to another tab and then back to the singular resource tab, the api endpoint doesn't get called again. Any ideas? – flynfish Jul 11 '13 at 21:22
2

Here is how I got this working in Ember 1.9. First I read this section of the guide. At the very bottom it explains how to override an adapter for just one model.

App.CartAdapter = App.ApplicationAdapter.extend {
  pathForType: ->
    'cart'
}

The pathForType function is where the pluralization happens (at least in the RESTAdapter which I am using) so none of the other functionality of the adapter gets affected (like host, or namespace).

snocorp
  • 31
  • 5
1

Just to share a more complete solution which is working for me - extend your app's ApplicationRouter (which itself extends DS.RESTAdapter).

App.CartAdapter = App.ApplicationAdapter.extend({
    pathForType: function(type) {
        return 'cart';
    }
});

Next, define your resource in App.Router.map:

this.resource('cart');

Finally, pass an empty string as the id in your route. This is what allows the URL to be generated without an id.

App.CartRoute = Ember.Route.extend({
    model : function(params) {
        return this.store.find('cart', '');
    }
});
Michael Edgar
  • 350
  • 3
  • 12