ember-django-adapter

Coalescing Find Requests

When a record returns the IDs of records in a hasMany relationship, Ember Data allows us to opt-in to combine these requests into a single request.

Note: Using hyperlinked related fields to retrieve related records in a single request is preferred over using coalesceFindRequests since there is a limit on the number of records per request on read-only fields due to URL length restrictions.

Suppose you have Ember models:

// app/models/person.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  pets: DS.hasMany('pet', { async: True })
});


// app/models/pet.js

import DS from 'ember-data';

export default DS.Model.extend({
  age: DS.attr('number')
});

An out-of-the-box DRF model serializer for Person would return something like this:

GET /api/people/1/

{
  "id": 1,
  "name": "Fred",
  "pets": [1, 2, 3]
}

When Ember Data decides to resolve the pets, by default it would fire 3 separate requests. In this case:

GET /api/pets/1/

{
  "id": 1,
  "age": 5
}

GET /api/pets/2/

{
  "id": 2,
  "age": 5
}

GET /api/pets/3/

{
  "id": 3,
  "age": 6
}

However, if we opt-in to coalesceFindRequests, we can consolidate this into 1 call.

Enable coalesceFindRequests

Extend the adapter, and enable coalesceFindRequests:

// app/adapters/application.js

import DRFAdapter from './drf';

export default DRFAdapter.extend({
  coalesceFindRequests: true
});

Now, when Ember Data resolves the pets, it will fire a request that looks like this:

GET /api/pets/?ids[]=1&ids[]=2&ids[]=3

CoalesceFilterBackend

All this is great, except Django REST Framework is not quite able to handle such a request out of the box. Thankfully, DRF allows you to plug in custom filters, and writing a filter for this kind of request is super simple.

In your project somewhere, write the following filter:

# myapp/filters.py

from rest_framework import filters


class CoalesceFilterBackend(filters.BaseFilterBackend):
    """
    Support Ember Data coalesceFindRequests.

    """
    def filter_queryset(self, request, queryset, view):
        id_list = request.query_params.getlist('ids[]')
        if id_list:
            # Disable pagination, so all records can load.
            view.pagination_class = None
            queryset = queryset.filter(id__in=id_list)
        return queryset

Now you just need to add this filter to filter_backends in your views, e.g.:

from myapp.filters import CoalesceFilterBackend


class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer = UserSerializer
    filter_backends = (CoalesceFilterBackend,)

Or, configure it globally in your DRF settings:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('myapp.filters.CoalesceFilterBackend',)
}

Now, when Ember Data sends the coalesced request, DRF will return meaningful data:

GET /api/pets/?ids[]=1&ids[]=2&ids[]=3

[
  {
    "id": 1,
    "age": 5
  },
  {
    "id": 2,
    "age": 5
  },
  {
    "id": 3,
    "age": 6
  }
]