Willem de Wit
Willem de Wit

Reputation: 8742

Query params in async hasMany via link

With Ember Data you can do this:

App.Post = DS.Model.extend({
   comments: DS.hasMany('comment', {async:true})
});

with the links-object you return from the server or serialize payload client-side like this:

{
  post:{
    id: 1,
    links: {
      comments: 'comments'
    }
  }
}

Ember Data generates the following url /post/:post_id/comments when you do post.get('comments')

But I got a lot of comments and I'd like to pass query params to it (just like a normal search in ember data): post.get('comments',{ from: '2014-01-01', to: '2014-01-07' });

Unfortunately it's not working. Is there another way?

See also my other question

Upvotes: 2

Views: 1707

Answers (3)

Michael de Hoog
Michael de Hoog

Reputation: 351

I have written an addon to accomplish this: https://github.com/mdehoog/ember-data-has-many-query.

After installing and configuring, you should be able to query:

post.query('comments', { from: '2014-01-01', to: '2014-01-07' });

Upvotes: 4

hewsonism
hewsonism

Reputation: 424

I had to solve this problem. I couldn't find a clean solution and looking at Ember Data's source code I couldn't imagine something better. Perhaps this will help someone else.


Given a relationship like this and the need to pass query params to tell the API what comments need to be returned (e.g. you need to include a 'search' param or 'pageNumber' / 'pageSize' for pagination).

App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', {async:true})
});

I added a key 'params' to the options object of the hasMany attribute. The value tells the adapter what other model attribute contains params related to that hasMany relationship. (Note that 'object' isn't a default EmberData attribute type see this gist to add that type.)

App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', {async:true, params: 'comment_params'}),
  comment_params: DS.attr('object'),
});

With the model modified to provide query params we need our adapter to consume the information:

App.ApplicationAdapter = DS.RESTAdapter.extend({
  findHasMany: function(store, snapshot, url, relationship) {
    var id   = snapshot.id;
    var type = snapshot.typeKey;

    url = this.urlPrefix(url, this.buildURL(type, id));

    if ('params' in relationship.options) {
      var params = snapshot.attr(relationship.options.params);
      if (params && Em.keys(params).length) {
        var queryParams = [];
        _.forEach(params, function (value, key) {
          queryParams.push(
            '%@=%@'.fmt(encodeURIComponent(key), encodeURIComponent(value))
          );
        });
        url = url + '?' + queryParams.join('&');
      }
    }

    return this.ajax(url, 'GET');
  },
});

Now you can use actions in routes/controllers or observers in your controllers to modify the params attribute, then call get() or reload() on your hasMany attribute:

  // some controller ...
  // assume a template with binding to 'query'
  queryDidChange: function() {
    var model = this.get('model');
    var params = model.getWithDefault('comment_params', {});

    var value = this.get('query').trim();
    if (value !== '') {
      params['search'] = value;
    } else {
      delete params['search'];
    }
    model.set('comment_params', params);
    // calling reload to get a new set of comments
    // if comments have not been loaded yet only the 'get' is
    // necessary.
    model.get('comments').reload();
  }.observes('query'),

Editorial: I really wish I didn't need to add all this code, but it seems that returning all records for a hasMany is the default with no out of the box way to alter that.


Edit #2: I use '_.forEach' from lodash. You could also use

Object.keys(params).forEach($same_foreach_function_as_above)

Upvotes: 1

Kingpin2k
Kingpin2k

Reputation: 47367

There are a slew of ways to accomplish this, I'm gonna list them all out then maybe decide which seems the best,

Change the json returned from the server (Probably the most correct)

{
  post:{
    id: 1,
    links: {
      comments: 'comments?from=foo&to=bar'
    }
  }
}

Example: http://emberjs.jsbin.com/OxIDiVU/155/edit

Modify the json in the serializer when you receive it

App.ColorSerializer = DS.RESTSerializer.extend({
  extractArray: function(store, type, payload) {
    console.log(payload);
    payload.colors.forEach(function(color){
      color.links.items += "?foo=bar";
    });
    return this._super(store, type, payload);     
  }
});

Example: http://emberjs.jsbin.com/OxIDiVU/156/edit

Modify the request in the adapter to include extra info in certain cases

App.ColorAdapter= DS.RESTAdapter.extend({
    findHasMany: function(store, record, url) {
    var host = Em.get(this, 'host'),
        id   = Em.get(record, 'id'),
        type = record.constructor.typeKey;

    if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') {
      url = host + url;
    }
    // check if post comments
    var builtUrl = this.buildURL(type, id),
        withPrefix = this.urlPrefix(url, builtUrl),
        data = {from:'foo'};
      return this.ajax(withPrefix, 'GET',{data:data});
  },
});

Example: http://emberjs.jsbin.com/OxIDiVU/154/edit

Upvotes: 4

Related Questions