Best practice? Ember Data, APIs, and basic vs. extended model properties

Hello all,

I’m running into a common pattern over and over again with Ember Data + Rails API + ActiveModel Serializers and am wondering what others are doing, if there’s a best practice, and feedback on my current approach.

Let’s say I have a Product model. When the user is at /#/products, the user should see a list of products with only the basic information: name, picture, and price. When the user goes to a product page, such as /#/products/1, he should see all the extended information about the product: reviews, variations, similar items, etc. Likewise, it seems to make sense to structure the API similarly: /products returns the basic information about many products, and /products/1 returns the extended information about a single product.

On the Rails side, I have two different Product serializers: one for the basic version and one for the detailed version, and the controller determines which serializer to use. Ember Data seems pretty happy with this approach, with one exception: when the user visits the /#/products page and then visits /#/products/1, the basic model is already loaded, so it will not necessarily know to reload the model to get the extended information. My solution for that is to define a DS.attr called “_received” that the detailed serializer will return as true. When the ProductIndexRoute retrieves the model, it calls reload() if _received is false.

The approach works fairly well, but it seems like a non-standard approach to a common problem. Is there a more standard approach? Anyone solving the problem differently?

Cheers, Chris

1 Like

I haven’t had to deal with this, but I my instinct would be to change the /#/products/:id route model hook to load the model with query parameters:

model: function() {
    var id = this.modelFor('product').get('id');
    return this.store.findQuery('product', { id: id, full: true });
}

I don’t know if this syntax is totally correct, but something similar I believe will force a reload of the model and your API can provide all the properties.

I’ve handled this before by splitting the model into two and make them related to each other through belongsTo.

Something like:

App.ProductModel = DS.Model.extend({
  /* Basic Properties */,
  details: DS.belongsTo("productDetail", {async: true})
});

App.ProductDetailModel = DS.Model.extend({
  /* Detailed Properties */,
  basicData: DS.belongsTo("product")
});

Then in the router I leveraged the afterModel hook to make sure you can handle loading state, errors, etc. Something like this:

App.ProductDetailRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find("product", params.product_id);
  },
  afterModel: function(model) {
    return model.get("details");
  }
});

Thanks so much for the feedback, gentlemen. Really appreciate your time and knowledge.

@buuda, it seems like our approaches are very similar. The full=true query parameter does the same job as my _received property. I believe that findQuery() will return an array, so you’d have to select the first object in the array as well.

@Spencer_Price, you have a pretty different approach… very interesting. I like how it works out of the box using all of the basic conventions. The split of the models feels a little artificial, but it can definitely be a good price to pay for keeping everything conventional.

1 Like