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


#1

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


#2

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.


#3

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");
  }
});

#4

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.