Adding query parameters to a hasMany relationship

Last reviewed on June 29, 2019 with Ember Octane

Your hasMany query returns a ton of results that you may decide to filter or paginate. Comments on a popular blog post (with a nested URL such as /api/posts/1/comments?page=1) is a great example.

Or perhaps your backend API requires an additional parameter for some specific operation.

Unfortunately, Ember Data does not support passing query parameters to relationships. How do we deal with this situation?

Use ember-data-has-many-query

This add-on allows us to directly add query params to hasMany (and belongsTo) associations. By simply including two mixins we can start querying associations from our model:

post.query('comments', { page: 1 });

I recommend using this add-on! For more info see the documentation.

If you have a more specific use-case (or want to understand what happens behind the scenes) we’ll attempt a basic implementation without the help of an add-on.

Overriding the adapter

URL or location customizations are done in an adapter.

But what is the adequate method to override? In the case of hasMany, we should be using findHasMany which is available in RESTAdapter and related adapters like JSONAPIAdapter.

That is, if we want to dynamically provide a page number for the Post’s comments, we have to override findHasMany in the PostAdapter. We retrieve a previously set property (query-params) with the desired values, and lastly turn this object into a query parameter string:

// app/adapters/post.js

import DS from 'ember-data';
import queryString from 'query-string';

export default class PostAdapter extends DS.JSONAPIAdapter {
  findHasMany(store, snapshot, url, relationship) {

    const id = snapshot.id,
      type = snapshot.modelName;

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

    if (relationship.type == "comment" && snapshot.record.get('query-params')) {
      const qp = queryString.stringify(snapshot.record.get('query-params'));
      url += "?" + qp;
    }

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

Before accessing the hasMany association again, we set the query-params property and trigger a reload. (Alternatively the reload can be done through fine-grained cache control).

Here’s a usage example:

paginate(page) {
  const post = this.model;
  post.set('query-params', { page: page });
  post.comments.reload();
}

Which means that a setting like post.set('query-params', { from: '2014-01-01', to: '2014-01-10' }) will result in a /api/posts/1/comments?from=2014-01-01&to=2014-01-10 call to the backend.

Enjoyed this article? Join Snacks!

Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)