What is the correct way to persist child model?


#1

Problem

I’m teaching myself Ember and having problems persisting my child models. Here’s a JSBin that demonstrates the problem:

If you play with it, you will be able to create Posts and Comments, but only the Posts are still available after a page reload. (I’m using ember-data and the localStorage adapter)


There are two model types, Post and Comment

App.Post = DS.Model.extend({
  title: DS.attr('string'),
  comments: DS.hasMany('comment')
});

App.Comment = DS.Model.extend({
  message: DS.attr('string')
});

Each Post has an array of Comments. I intend there to be the following URLs:

  • /posts will list all the Posts
  • /posts/1 will display the Post with id 1
  • /posts/1/comments will display the Comments for Post 1

Routes are defined like so:

App.Router.map(function() {
  this.resource("posts", { path: "/posts" });
  this.resource("post", { path: "/posts/:post_id" }, function() {
    this.resource("comments", { path: "/comments" });
  });
});

I am able to write Post models to the store without problem. What I cannot do is save the Comment models. If you play with my JSBin example above you will see that you can create comments but they are not persisted when reloading the page.

I attempt to save Comments in the CommentsController:

App.CommentsController = Ember.ArrayController.extend({
    needs: "post",
    post: Ember.computed.alias("controllers.post"),
    newMessage: '',
    actions: {
        create: function() {
            var message = this.get('newMessage');

            var comment = this.store.createRecord('comment', {
                message : message
            });

            this.set('newMessage', '');
            
            var post = this.get('post');
            var comments = post.get('comments');
            comments.pushObject(comment);
            comment.save();
        }
    }
});

Notice that I am getting a handle on the parent Post by using the needs property. Taken from the documentation about dependencies between controllers.

This all feels wrong as it should be a common situation so I suspect I’ve designed my models or routes in a non-standard way.

Thanks, Matt


#2

I’m having similar issues (I think). Have you looked at your localstorage json? I’m ending up with json that has the entire record stored in the hasMany array like so:

{
  id : '0sv3',
  name : 'newList',
  cards : [
    {
      name : 'card1'
    },
    {
      name : 'card2'
    }
  ]
}

Ember expects the cards array to be a list of ID’s for the cards that are contained in the cards namespace.

{
  id : '0sv3',
  name : 'newList',
  cards : [
    '023xe',
    'seer3'
  ]
}

What I’m struggling to find is what layer is responsible for this, is is EmberData or is it the LocalStorage Adapter itself. Or do I need to create a custom adapter that extends the LocalStorage Adapter.

I just found this issue on the local storage adapter repository also, looks like a common issue: https://github.com/rpflorence/ember-localstorage-adapter/issues/23


#3

I solved my original problem but am now facing the same problem you are describing. It’s starts out as an array of ids then suddenly flips into embedded models. I’ve posted it as a question on stackoverflow but no clues yet.

It’s so frustrating, I’m not tying to do anything complicated, just a simple blog post example to learn the basics. I’m on the brink of giving up :frowning:


#4

For completeness, my original problem was due to not saving the Post object after adding comments. This thing I missed was that the alias for ‘post’ in the CommentsController was for the PostController not the model. I simply needed to do something like post.get(‘model’).save(). Full discussion and example here: http://stackoverflow.com/questions/18792696/what-is-the-correct-way-to-persist-child-model-with-ember-js


#5

Hi, did you solve this in the end?


#6

I actually haven’t solved it yet, I’ve been too busy with other things to pick it back up and look deeper into the issue. As soon as I figure it out I’ll try to remember to update this thread.


#7

I’ve not really gotten to the bottom of the cause, but I’ve come up with a solution that fixes the symptoms! I basically modified your adapter to only write “corrected” json when persisting to localstorage. I’ll fork your repo and send you a pull request so you can take a look at my solution and fold it into your code if you wish. Cheers, Matt


#8

@mattburns I’m looking forward to seeing how you solved this problem.


#9

My previous solution was nothing more than a bad plaster.

The real solution is two-fold. First you have to define a belongsTo relationship on the Comment:

App.Comment = DS.Model.extend({
    message: DS.attr('string'),
    post: DS.belongsTo('post')
});

Then you need to change the serializer because it doesn’t seem to work properly (I think it’s a bug but am not 100%).

To solve it simply override the implementation of “serializeHasMany” in JSONSerializer by putting this in your application:

/**
 * Have to do this to enable arrays of ids to be written in parent models
 * with 'hasMany" relationships
 */
DS.JSONSerializer.reopen({
    serializeHasMany : function(record, json, relationship) {
        var key = relationship.key;
    
        var relationshipType = DS.RelationshipChange.determineRelationshipType(
                record.constructor, relationship);

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