Ember Data 1.0.0 Beta: code snippets on how to use a hasMany relationship


#1

Ember Data 1.0 Beta changed a lot for most of us … A topic I see often reappear on this forum and on Stackoverflow is the hasMany relationship. In this post I will give some additional information and code snippets that might help you.

Do not hesitate to provide feedback with your findings.

**1. Use the latest canary build and verify the serializeHasMany method **

Yes, you are working with bleeding edge technology. In Ember Data 1.0 Beta 1 and Beta 2, hasMany relationships do not work. The team is working hard and the latest canary build (dated sept 11) provides “almost” a solution. Open the canay build and verify that in the serializeHasMany method, the following is used in the if statement (thus 3 || statements):

if (relationshipType === 'manyToNone' 
 || relationshipType === 'manyToMany' 
 || relationshipType === 'manyToOne') 

2. Defining the models

Identical to previous versions, the parent model should include a hasMany relationship. See code snippet below:

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

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

3. Adding a child record to the parent

You will probably implement this in a controller; in my case, I called the controller CommentNewcontroller. The proces to add a child to a parent is as follows:

  1. Create a child record
  2. Save the child record
  3. If the save is succesful, you will receive an id for the child
  4. Add the child to the parent
  5. Save the parent

Below is my code snippet (some rough error handling is added, but should be further detailed):

App.CommentNewController = Em.ObjectController.extend({
  needs: ['postsShow'],
  
  commenttext: null,

  actions:{

    addComment: function (body) {
      this.set('commenttext', null);
    },

    save: function () {
      //Create a new comment record
      var comment = this.store.createRecord('comment', {
        commenttext: this.get('commenttext')
      });
     
      //The post to be updated
      var post = this.get('controllers.postsShow.content');
      
      //Save the comment and then the post
      comment.save().then(
        function () {
          //Succesful save of comment; thus add to post
          post.get('comments').addObject(comment);
          
          //Save post
          post.save().then(
            function () {
              //Succesful save of post
            },
            function (error) {
              console.log("API error occured - " + error.responseText);
              alert("An error occured - REST API not available - Please try again");
            }
          );
        },
        function (error) {    
          console.log("API Error occured - " + error.responseText);
          alert("An error occured - REST API not available - Please try again");
        }
      );
    }
  }
});

Please make sure to use Ember Inspector when testing your “save” code; at any time, the models at client side should be in sync with the server !

REST API call when saving the child (in this case a comment):

POST Request:

{
 comment:{
  commenttext:Comment 123
 }
}

Expected Response:

{
  "comment": {
    "commenttext": "Comment 123",
    "id": "52306b97d8861e281400001a"
  }
}

REST API call when saving the parent (in this case a post):

PUT Request:

{post:{
   title: "Post 123"
   body: "<p>POST 123</p>"
   comments: [0: "52306b97d8861e281400001a"]
 }}

Expected Response:

   {
    "post": {
      "title": "Post 123",
      "body": "<p>POST 123</p>",
      "comments": [
          "52306b97d8861e281400001a"
        ],
      "id": "52306b8cd8861e2814000019"
  }
}

4. Reading (getting a parent and the childs)

When getting a post record, the child records should be referenced, but not embedded in the parent. Embedded childs are at this moment not supported. You should also make sure that your REST API has an interface to get the child records in batch (thus get multiple childs in one call). Ember Data will automatically fetch the child records via a seperate API call.

When loading a post with 2 comments, the API calls are as follows:

Get the post:

http://localhost:3000/posts/52306b8cd8861e2814000019

Expected response:

{
  "post": {
    "id": "52306b8cd8861e2814000019",
    "title": "Post 123",
    "body": "<p>POST 123</p>",
    "comments": [
      "52306b97d8861e281400001a",
      "52306e8dd8861e281400001b"
    ]
  }
}

Get the child records:

http://localhost:3000/comments?ids[]=52306b97d8861e281400001a&ids[]=52306e8dd8861e281400001b

Hope this article can take away some of the confusion with Ember Data and the hasMany relationships …

Marc


#2

Since you’re talking about something I need help with, could you perhaps offer input on this issue I’m having with one to many relationships?


Ember-Data beta 3 and saving hasMany relationships with ActiveModelSerializer and Rails
#3

I cannot really help in this area.

I see that you are using Ember Data rc13 which is now completely rewritten, thus I would in any case recommend to not further develop on rc13 (this will never become a v1 release).

What is the use case or need to put an observer in the model of a child that watches the parent for changes? When do you expect the observer to trigger? I have tried this in Ember Data 1.0 Beta 2 and the observer method does not fire when loading


#5

Thanks for the post, very helpful…

I’m assuming this is a typo:

comments: [0: "52306b97d8861e281400001a"]

Wouldn’t it be:

comments: ["52306b97d8861e281400001a"]

I’d also recommend mentioning that side-loading your comments is an option. Our app is in mongo, and we use embedded documents for our hasManys. The way I handle this is to just separate them server-side and return them side-loaded. Works great!


#6

I followed your steps but my “then” function never fires:

    t1.save().then(function(){
      console.log('never fires');
    });

The model gets saved on the server but I need the “then” to fire.

Any idea?


#7

Hi cyclomar. Are you planning to support embedded childs in a future release? If you have a model that has many of the same model (e.g. a categories hierarchy) it feels that an embedded format can be better.


#8

from this line

var post = this.get(‘controllers.postsShow.content’);

I wonder how ember know which parent ids that comment will be added to? Do relevant route have to be nested?