Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember Data: model and hasMany submodels not persisting

I have a problem similar to this stack overflow question, except the answer doesn't seem to be working. I have a form in which the user creates a container module with a variable number of submodels. When the form is submitted, I have to save the container, the submodels, and make sure that the the hasMany relationship persists. My code (using Ember-Cli):

container:

var Container= DS.Model.extend({
    name: DS.attr('string'),
    submodels: DS.hasMany('submodel'),
    lastModified: DS.attr('date')
});

export default Container;

submodel:

    var Submodel= DS.Model.extend({
        char: DS.belongsTo('container'),
        name: DS.attr('string'),
        desc: DS.attr('string'),
        priority: DS.attr('number'),
        effort: DS.attr('number'),
        time: DS.attr('number')
    });

export default Submodel;

ContainersNewRoute:

 export default Ember.Route.extend({
        model: function() {
            return this.store.createRecord('container', {
                ...
            });
        }
    });

ContainersNewController:

export default Ember.ObjectController.extend({
    actions: {
        addSubmodel: function() {
            var submodels= this.get('model.submodels');

            submodels.addObject(this.store.createRecord('submodel', {
                ...
            }));
        },
        saveNewContainer: function () {
            var container = this.get('model');

            container.save().then(function(){
                var promises = Ember.A();
                container.get('submodels').forEach(function(item){
                    promises.push(item.save);
                    console.log(item);
                });
                Ember.RSVP.Promise.all(promises).then(function(resolvedPromises){
                    alert('all saved');
                });
            });

            this.transitionToRoute(...);
        }
    }
});

The Ember Data itself works fine, transition to a view of the created container, with submodels listed. Refresh the page, and the submodels disappear from the container view.

I've tried a few variations, for example using pushObject rather than the addObject from the stack overflow answer. I've also tried using the Ember.RSVP callback to run container.save() a second time after the submodels have been saved.

After some further testing, I've found that the submodels are not persisting at all.

Is there a sane way to save 1) the container 2) the submodels 3) the hasMany/belongsTo relationships to each other?

Or does this some how need to be broken down into discrete steps where I save the container, save the submodels, the push the submodels to the container to get the hasMany relationship and resave the container, and lastly make the submodels belongTo the container and save the submodels again?

like image 890
ansorensen Avatar asked Jun 13 '14 19:06

ansorensen


2 Answers

The Problem

By default, DS.hasMany on a one to many association won't include ids field when serializing. You can use DS.EmbeddedRecordsMixin to change this behavior.

More Info

Please read Embedded Records Mixin section on A Thorough Guide to Ember Data for more. (disclaimer, I'm its author.)

Here is an excerpt

DS.EmbeddedRecordsMixin is an extension for DS.ActiveModelSerializer which allows configuring of how associations get serialized or deserialized. Although not yet complete (especially with regard to polymorphic associations), it is intriguing nonetheless.

You can choose:

  • Not to serialize or deserialize associations.
  • To serialize or deserialize associations with id or ids.
  • To serialize or deserialize associations with embedded models.

Code Sample:

App.CartSerializer = DS.ActiveModelSerializer
                       .extend(DS.EmbeddedRecordsMixin)
                       .extend{
                         attrs: {
                           items: {serialize: 'ids', deserialize: 'ids'}
                         }
                       });

App.Cart = DS.Model.extend({
  items: DS.hasMany('item', {async: true})
});

App.Item = DS.Model.extend({
  cart: DS.belongsTo('item', {async: true})
});
like image 155
Pooyan Khosravi Avatar answered Sep 27 '22 16:09

Pooyan Khosravi


You have the problem that I've experienced earlier, you have easier example than my cause you need only one relation :hasMany and :belongsTo

Try to use this approach:

export default Ember.ObjectController.extend({
// here we can pre-load submodels container, 
//even if it's empty because 'this.get('model.submodels')' returns promise

submodels: function () { 
    return this.get('model.submodels');
}.property('[email protected]'),

actions: {
    addSubmodel: function () {
        //saving context of controller, saving container for 
        //better understanding (clear vision)
        var controller = this, 
            container = controller.get('conrtainer');
        //container  instead of 'model' for better understanding :)

        //1. Creating submodel
        var submodel = this.get('store').createRecord('submodel', {
            // ... //
            container: container
        });

        //2. Saving submodel
        submodel.save().then(function (submodel_res) {

            //3. It isn't the end though :) 
            //Now we're getting our submodels from the container - here we will get
            //the result instead of promise, remember we pre-loaded it :) -1 nesting lvl
            controller.get("submodels").pushObject(submodel_res);

            //4. Now we need to update our 'parent' model - Container
            container.save().then(function (post_res) {
                console.log(post_res); // 5. Doesn't matter we are happy
            }, function (err) {
                console.log(err);
            });
        }, function (err) {
            console.log(err);
        });
      }
   }
});

 //In order to use this approach you need to override property 
 //in model serializer (just copy it    and //paste :) )

 YourApp.ContainerSerializer = DS.ActiveModelSerializer.extend({ 
 // here coulbe be RESTSerializer as well ;)

 primaryKey: function () {
    return '_id';
 }.property(), 
 // it's just helpful tip, if you use mongo like me ;) 
 //it doesn't relates to the main topic itself

 // this little method will help you update 'parent' model ;)
 serializeHasMany: function (record, json, relationship) { 
    var key = relationship.key;
    var json_key = key.singularize().decamelize() + '_ids';

    var relationshipType = DS.RelationshipChange.determineRelationshipType(
        record.constructor, relationship);

    if (relationshipType === 'manyToNone' 
    || relationshipType === 'manyToMany' 
    || relationshipType === 'manyToOne') {
        json[json_key] = Ember.get(record, key).mapBy('id');
    }
 }
});

Good luck ;)

like image 27
Blackening Avatar answered Sep 27 '22 17:09

Blackening