How do I manually populate a DS.Model?

I have an endpoint, /bulk-page-data that returns the API data for several models needed for rendering the page (current account, current user, locale information, settings, etc.). I want to GET /bulk-page-data in my ApplicationRoute#model, then populate several different models with that data.

Attempt 1

I tried just using Ember.$.getJSON('/bulk-page-data') in the #model hook. That works, but I don’t have a way to pass the resulting data through the various serializers when instantiating the models, nor a way of telling the store about those models. Thus, I have the data, but not in a form that my other models can use.

Attempt 2

I tried creating a BulkPageData model with belongsTo relationships to all the other models it populates (using DS.EmbeddedRecordsMixin), but Ember-Data complains that it can’t find the inverse relationships. Adding { inverse: false } to the belongsTo call didn’t seem to help.

did you try inverse: null

I have not yet tried inverse: null. I’ll give that a shot if my current attempt doesn’t pan out.

Attempt 3

Abandon the BulkPageData model, go back to Attempt 1, and use this.store.createRecord('person', personData). See Store - 4.6 - Ember API Documentation

I have had success passing a simple pojo to the createRecord method similar to what it looks like you are doing in Attempt 3. You might have to make sure the pojo is sufficiently normalized before adding to createRecord, so the attrs line up.

Attempt 3 seems to be working, but @lukemelia suggests that pushPayload might be a better approach, long-term. My trouble there is that my API doesn’t (generally) return root elements, so Ember wouldn’t have a way of determining which model the payload is for.

I tried writing an ApplicationSerializer that would insert the root element if it’s missing. (This is probably something I’ll need in my app in general, so thanks, Luke, for pushing me to do it now!) Unfortunately, I can’t figure out how to test it.

Ember-Data’s serializer tests require a huge amount of setup; I’d have to copy all of that support code into my app to test this serializer.

Has anyone had success with Serializer unit tests?

Attempt 4

I’m trying the pushPayload version. This works, though the code is a bit convoluted.

model: function() {
  var store = this.store;

  return new Ember.RSVP.Promise(function(resolve) {
    Ember.$.getJSON('/bulk-page-data').then(
      function(json) {
        store.pushPayload({
          people: [ json.user ],
          accounts: [ json.account ]
        });

        resolve({
          person: store.getById('person', json.user.id),
          account: store.getById('account', json.account.id),
        });
      },
      resolve
    );
  });
},

Have you looked at using Ember Data’s JSONSerializer as your ApplicationSerializer? It is a serializer that ships with Ember Data that does not assume a root element.

You can be able to use store.normalize to run the json through the serializer before adding it to the store with store.push.

Simplified example assuming the use of the JSONSerializer.

return Ember.$.getJSON('/bulk-page-data').then(function(json) {
        return {
          person: store.push('person', store.normalize('person', json.user)),
          account: store.push('account', store.normalize('account', json.account)),
        };
});
2 Likes

I did, but I need the camelization/decamelization of the ActiveModelSerializer. I could port that to a subclass of JSONSerializer, but I’m left with the same problem: I can’t test a serializer.

Some things I’ve tried in testing:

Stubbing

var serializer = ApplicationSerializer.create();
var store = {
  modelFactoryFor: function() { return Ember.Object; },
  modelFor: function() { return Ember.Object.create(); }
};
var type = {
  typeKey: 'post',
  eachAttribute: Em.K
}
return serializer.extractSingle(store, type, apiJSON, someID);

I’m having trouble figuring out the right stubs. That doesn’t do quite what I want. It’s also super brittle because it’s tied to the internal implementation of ActiveModelSerializer.

Using moduleFor & createRecord

moduleFor('serializer:application', 'ApplicationSerializer#extractSingle', {
  setup: function(container) {
    var Post = DS.Model.extend({
      title: DS.attr('string')
    });

    container.register('model:post', Post);
    container.register('store:main', DS.Store);
    store = container.lookup('store:main');
  }
});

combined with store.createRecord(apiJSON). But here I get “Error: Assertion Failed: {title: 14 Donuts You'll Love, store: <DS.Store:ember195>} does not appear to be an ember-data model.”

You could create the model record locally and do a peekAll on the store :slight_smile:

e.g.:

import Route from "@ember/routing/route";

let captions = [
  {
    id: 1,
    text: "Sound check",
    start: "00:00:00,000",
    end: "00:00:10,140"
  },
  ...
];

export default Route.extend({
  model() {
    captions.forEach(caption => {
      this.store.createRecord("caption", caption);
    });

    return this.store.peekAll("caption");
  }
});