Ember.js Recipes: On-Demand Record Details With Ember-Data

A common source of frustration in Ember-Data is the inability to load partial records. You probably have a model in your application that only needs a small subset of its attributes serialized in the index response, but which has details that are either data-heavy or expensive to compute, so you’d rather only return them as needed.

For example, imagine a blog’s Post model. You may not want to return each post’s body when a user only wants to see an index of posts in a search result or archive page. You may also have statistics associated with Post records, such as the number of comments or trackbacks, that require additional database queries to compute.

Some data persistence libraries for Ember.js (like Emu) support partial loading out of the box. In Emu, your /posts endpoint can return [{id: 1, title: "Such post"}], and your /posts/1 endpoint can return {id: 1, title: "Such post", body: "Many text"}. Emu will make a request to the latter endpoint when the body attribute is requested and load it.

Ember-Data does not currently support this behaviour by default (as of 1.0.0-beta.4 when this was written), but it provides you with the tools to add it yourself, with relatively little effort.

We will add a PostDetail model with the missing attributes, and load it as needed. We can structure our API a couple of different ways to do this, but our front-end models will look the same:

1
2
3
4
5
6
7
8
App.Post = DS.Model.extend({
  detail: DS.belongsTo('postDetail', { async: true }),
  body:   Ember.computed.alias('detail.body')
});

App.PostDetail = DS.Model.extend({
  body: DS.attr('string')
});

Simple, right? The PostDetail model holds the actual body attribute, but we alias it on the Post model. If we use {{post.body}} in a template, the PostDetail record will be requested if necessary.

Okay, but how do we structure our API to make this work? If you only have to fetch, and never save detail attributes, we can add a single /posts/1/detail endpoint to our API and link to it in our Post JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// GET /posts
{
  posts: [{
    id: 1,
    links: { detail: "/posts/1/detail" }
  }]
}

// GET /posts/1/detail
{
  post_detail: {
    id: 1,
    body: "..."
  }
}

(All examples here assume we are using the built-in ActiveModelAdapter.)

Alternatively, we can add a /post_details endpoint, responding to whatever actions you need to support,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// GET /posts
{
  posts: [{
    id: 1,
    detail_id: 1
  }]
}

// GET /post_details/1
{
  post_detail: {
    id: 1,
    body: "..."
  }
}

You may be concerned that if a user loads the page for a post, your application will need to make two HTTP requests instead of just one. But we can fix this too. Your show action can return the post details sideloaded:

1
2
3
4
5
6
7
8
9
10
11
// GET /posts/1
{
  post: {
    id: 1,
    detail_id: 1
  },
  post_details: [{
    id: 1,
    body: '...'
  }]
}

There you have it! □

Comments