Tails
Tails

Reputation: 712

Load empty/optional embedded hasMany relation with Ember Data

Where most people have problems loading embedded Ember models, I have a problem with the exact opposite.

Ember throws errors when I try to parse a record that contains an embedded relationship, where the content for that hasMany relation is an empty array.

How do you make an embedded Ember Data hasMany relation optional or nullable?

I have a model..

App.Beat = DS.Model.extend({

  notes: DS.hasMany('note', {
    embedded: 'always', 
    defaultValue: []
  }),

  ...
})

This model is an association in a Bar model, which is an association in a Track model. Those don't really matter here.

These embedded hasMany relationships get serialized with the following serializer..

// http://bl.ocks.org/slindberg/6817234
App.ApplicationSerializer = DS.RESTSerializer.extend({

  // Extract embedded relations from the payload and load them into the store
  normalizeRelationships: function(type, hash) {
    var store = this.store;

    this._super(type, hash);

    type.eachRelationship(function(attr, relationship) {
      var relatedTypeKey = relationship.type.typeKey;

      if (relationship.options.embedded) {
        if (relationship.kind === 'hasMany') {
          hash[attr] = hash[attr].map(function(embeddedHash) {
            // Normalize the record with the correct serializer for the type
            var normalized = store.serializerFor(relatedTypeKey).normalize(relationship.type, embeddedHash, attr);

            // If the record has no id, give it a GUID so relationship management works
            if (!normalized.id) {
              normalized.id = Ember.generateGuid(null, relatedTypeKey);
            }

            // Push the record into the store
            store.push(relatedTypeKey, normalized);

            // Return just the id, and the relation manager will take care of the rest
            return normalized.id;
          });
        }
      }
    });
  }
});

After successfully deserializing and loading the records in the store, somewhere in the application the bars property on a Track gets accessed. If that Bar has beats of which one of those beats do not have any notes (because it is a rest beat where no notes get played) then the following error is thrown:

"You looked up the 'bars' relationship on '' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.hasMany({ async: true }))"

This error comes from the following assertion in ember-data.js:hasRelationship:

Ember.assert("...", Ember.A(records).everyProperty('isEmpty', false));

where records is the array of bars that contain beats that optionally contains notes.

So, how do I make an embedded hasMany relation optional so that it accepts an empty array of records?

Upvotes: 3

Views: 1247

Answers (2)

Tails
Tails

Reputation: 712

It turned our that it was the isEmpty on my bar model that conflicted with the property that was being tested in the Ember assertation. Renaming this property made everything work.

Upvotes: 0

Andrew Hacking
Andrew Hacking

Reputation: 6366

I recommend using the EmbeddedRecordsMixin in a recent Ember Data beta (10 or 11) and then see if you still have an issue with embedded records.

Your application serializer:

App.ApplicationSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin,{
   // probably nothing required here yet
});

And then in your Beat model serializer:

App.BeatSerializer = App.ApplicationSerializer.extend({
  attrs: {
    notes: { embedded: 'always' }
  }
});

Upvotes: 1

Related Questions