1
votes

My question is highly related to "Ember Data: Saving relationships" but I don't care about embedding - I just want a working OneToMany (bi-directional) relationship. Take for example the following models:

App.Child = DS.Model.extend({
    name: DS.attr('string'),
    toys: DS.hasMany('toy'),
});
App.Toy = DS.Model.extend({
    name: DS.attr('string'),
    child: DS.belongsTo('child')
});

and the following object creations/saves:

var store = this.get('store');

store.createRecord('child', {name: 'Herbert'}).save().then(child => {
    return store.createRecord('toy', {name: 'Kazoo', child: child}).save())
}).then(toy => {
    child.get('toys').pushObject(toy);
    return child.save();
});

I would expect the child, when serialized, to reference the toy. E.g. something like

{
   'name': 'Herbert',
   'toys': [ 1 ]
}

But it doesn't. Because this is a "manyToOne" relation ship and ember-data won't serialize these: https://github.com/emberjs/data/blob/v1.0.0-beta.18/packages/ember-data/lib/serializers/json-serializer.js#L656

If you make it a ManyToNone relation by removing the belongsTo it will work but you will lose the back reference.

Why is this special behaviour? Why is ManyToOne that different from ManyToNne or ManyToMany that it deserves such special treatment?

Where is this behaviour documented? I totally missed it and assumed it was a bug in the Serializer / Adapter I'm using.

What is the correct way to achieve my desired serialization?

1
why save child a second time? saving the toy the first time will give it a foreign key, and ember data's store is a single source of truth so all client relationships will work - Sam Selikoff
also you should return your second .then and chain promises, instead of nesting - Sam Selikoff
the second save is to persist the added toy, not that it makes any difference, the attribute remains empty (which is the whole point about my question). You're right about nesting vs chaining promises, but it makes no difference for the end result. - Ivo van der Wijk
maybe a better question is, what are trying to achieve? Why do you want the child to get serialized with an array of toy_ids? - Sam Selikoff

1 Answers

0
votes

I ended up creating my own, trivially modified serializer:

import EmberPouch from 'ember-pouch';

export default EmberPouch.Serializer.extend({
  serializeHasMany: function(snapshot, json, relationship) {
    // make sure hasMany relationships are serialized.
    // http://stackoverflow.com/questions/20714858/ember-data-saving-relationships
    var key = relationship.key;

    if (this._canSerialize(key)) {
      var payloadKey;

      // if provided, use the mapping provided by `attrs` in
      // the serializer
      payloadKey = this._getMappedKey(key);
      if (payloadKey === key && this.keyForRelationship) {
        payloadKey = this.keyForRelationship(key, "hasMany", "serialize");
      }

      var relationshipType = snapshot.type.determineRelationshipType(relationship);

      if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany' || relationshipType === "manyToOne") {
        json[payloadKey] = snapshot.hasMany(key, { ids: true });
        // TODO support for polymorphic manyToNone and manyToMany relationships
      }
    }
  },
});

the main difference being that it also accepts manyToOne relations,

and use it in application/adapter.js:

export default EmberPouch.Adapter.extend({
  defaultSerializer: "pouchserial",
....

In this specific (pouch) case it would probably be better to store the reference to the parent on the child only so the parent doesn't need updates when children are added (which also avoids conflicts).