Loading partial models then filling them with Ember-data

Hi All,

StackOverflow doesn’t seem to be much help and I’m looking to perhaps stir up some conversation around the best way to work around this issue. Relavant Issues and SO’s at the bottom of this post.

I have a list view and a detail view. The models being displayed are fairly large and take a while to gather from the server. Luckily, I can slim them down to only send partial information. This dramatically speeds up the retrieval of data.

The main issue I have stems from displaying a list and a detail in the same view, much like gmail’s mobile app or @tom’s youtube video…

If I reload at the detail view ember will fire off two calls:

  1. To /models/:model_id to retrieve the full details of the model
  2. To the slimmed list view api call, say, /models?slim_all=true

If 2 returns before 1 there are no issues, as 1 overwrites the partial model with a full model. However, if the opposite happens 2 will overwrite 1 and ember will update my handlebars to show an mostly empty model.

This problem doesn’t exist if the user starts from the list view as I call model.reload(); when the detail route is called.

Option 1: Create two sets of models. One for list and one for detail. However this isn’t ideal because you lose the performance benefits of passing the model from list view to detail view

Option 2: I’m not sure…


Unanswered SO: ember.js - Dynamically fill models with more detailed data in emberjs - Stack Overflow

Unanswered SO: ember.js - Handle partial or summary object list returned by GET /models using ember-data - Stack Overflow

Closed github issue: https://github.com/emberjs/data/issues/51

I’ve done something similar and found that replacing one model with another (when it’s time to load the more detailed in your example) works well. The only real issue I’ve had is that my current implementation is rather simple so I don’t know how it scales up.

When my application starts I pull down a set of configuration from my api

App.initializer({
    name: 'appBootstrap',
    initialize: function() {
        App.deferReadiness();
        $.getJSON("/api/days/").then(function(json) {
            var store = DS.get('defaultStore');
            var transaction = store.transaction();
            for(var i = 0; i < json.length; i++) {
                App.Configuration.add(json.objectAt(i), transaction);
            }
            App.advanceReadiness();
        }, function(error) {
            window.alert("an error occurred loading the app");
        });
    }
});

This configuration is the basis for a number of models and represents them in memory before I fetch a full blown / more detail version of that configuration. I add these with a custom transaction to keep them out of the global ember-data transaction so if I do a store.commit() they won’t get flushed to the server.

Now when it comes time to load a real model I fetch the persisted objects from my REST api real time.

appointments: function() {
    var slots = this.get('slots'); //load up the configuration

for(var i = 0; i < slots.get('length'); i++) {
    //add each configured model to the "shared" array of ember objects
    //showing both persisted and in memory objects together
}
return App.Appointment.find(); //so the handlebars template is bound
}.property()

Now the tricky part is when the App.Appointment.find() is resolved, I needed a way to replace each model that was already loaded.

App.Appointment = DS.Model.extend({
    start: DS.attr('date'),
    end: DS.attr('date')
}).reopenClass({
    records: [],
    find: function(day) {
        var self = this;
        var interval = day.get('interval');
        self.records.clear();
        $.getJSON('/api/appointments/', function(response) {
        for(var i = 0; i < response.length; i++) {

            for(var j = 0; j < appointments.get('length'); j++) {
                if (appointments[j].get('start') === response[i].start) {
                    appointments.replace(j, 1);
                }
            }
            appointments.addObject(App.Appointment.createRecord(response[i]));
        }
        });
        return this.records;
    },
    add: function(record) {
        this.records.addObject(App.Appointment.createRecord(record));
    }
});

Now while I’m not yet proud of the code above it works perfectly. I’m left with a 2 model array showing both persistence backed and configuration backed objects :slight_smile:

Not sure anyone else has had to hack something like this together but I’d love to see other solutions for something like this.

The only real downfall of the above (aside from complexity in it’s current form) is that I’m not using ember-data to manage the appointment model and at some point I’m sure I’ll regret that

3 Likes

I’ve been trying to figure this out for a while and think I finally stumbled on something that works pretty well for me, YMMV.

I just overrode the preprocessData function on my App.Store to check if the data I’m supplying is a full item or partial, based on the attribute basicInfo, then checks to see if the item is already in the Store, and returns accordningly Here’s what my App.Store looks like now

App.Store = DS.Store.extend(
  revision: 12

  preprocessData: (type, data) ->

    if data.basic_info && this.hasReferenceForId(type, data.id)
      return unless this.find(type, data.id).get('basicInfo')

    @_super(type, data)

)
2 Likes

For anyone else trying to do this, it looks like preprocessData has been deprecated.

I have achieved the same result by overwriting the stores push method instead.

App.Store = DS.Store.extend
  push: (type, data, _partial) ->

    if data.basic_info && this.hasReferenceForId(type, data.id)
      return unless this.find(type, data.id).get('basicInfo')

    @_super(type, data, _partial)

I think ember-data should handle this. Maybe just updating the fields that are empty when retreiving the same record.

https://github.com/BookingSync/ember-data-partial-model was made to address this exact issue.