Ember-data Relationships like polymorphic , async , inverse , embedded


#1

Can anyone explain about ember-data model relationships like polymorphic , async , embedded, inverse ? And when to use all these relationships ? I searched a lot and i found some explanations but i didn’t understand those properties . It helps me to design my models . Thanks in advance .


#3

In a nutshell,

async is not type of relationship, but more the way how data is loaded from the server.

With async: false, Ember Data would expect all relations data to be embed in one server response, for example GET /users/1:

{
  user: {
    id: 1,
    messages: [1, 2]
  },
  messages: [
    {
      id: 1,
      body: 'Test comment'
    },
    {
      id: 2,
      body: 'Test comment'
    }
  ]
}

If you set async: true, two requests will be made:

GET /users/1

{
  user: {
    id: 1,
    messages: [1, 2]
  }
}

GET /messages/?ids[]=1&ids[]=2

{
  messages: [
    {
      id: 1,
      body: 'Test comment'
    },
    {
      id: 2,
      body: 'Test comment'
    }
  ]
}

By default, relations should be ‘sideloaded’, embedded allows to list complete records instead of just IDs (more info here) GET /users/1:

{
  user: {
    id: 1,
    messages: [
      {
        id: 1,
        body: 'Test comment'
      },
      {
        id: 2,
        body: 'Test comment'
      }
    ]
  },
}

Polymorphic is when a model can hasMany not only one, but few different models. For example, user can have comments and posts in the same list. Here is a simple example (original article):

// User's messages can be posts or comments
App.User = DS.Model.extend({
 messages: DS.hasMany(App.Message, {polymorphic: true})
});

// Posts and messages should inherit from same parent class
App.Message = DS.Model.extend({
  created_at: DS.attr('date'),
  user: DS.belongsTo(App.User)
});

App.Post = App.Message.extend({
  title: DS.attr('string'),
  body: DS.attr('string')
});

App.Comment = App.Message.extend({
  body: DS.attr('string')
});

For inverse please find more information here: Defining Models.


#4

Thanks for your time in explaining all these concepts . I understood async and embedded . But polymorphic and inverse is not clear for me . For inverse from that documentation example, if we say

App.Comment = DS.Model.extend({
  onePost: DS.belongsTo('post'),
  twoPost: DS.belongsTo('post'),
  redPost: DS.belongsTo('post'),
  bluePost: DS.belongsTo('post')
});


App.Post = DS.Model.extend({
  comments: DS.hasMany('comment', {
    inverse: 'redPost'
  })
});

{post:{
id:1,
comment:[1,2,3]
},
comment:[
{
id:1,
onePost:1,
twoPost:1,
redPost:1,
bluePost:1
},
{
id:2,
onePost:1,
twoPost:1,
redPost:1,
bluePost:1
},
{
id:3,
onePost:1,
twoPost:1,
redPost:1,
bluePost:1
}
]
}

How it will update only redPost ? I didn’t get this one


#5

A polymorphic association is a type of association for which you don’t know the exact type of the associated record, only a base type.

The use case I always present is for a blog engine: a user can post blog posts or comments. So a user has many messages, that can be blog posts or comments.

Take a look at this gist https://gist.github.com/Cyril-sf/515085e856f9dac2f06a to how this is used in Ember.


#6

Thanks for your detailed explanation about polymorphic association


#7

Could someone please explain the inverse property in more detail…or even better, explain the example used in the documentation:

var belongsTo = DS.belongsTo,
hasMany = DS.hasMany;

App.Comment = DS.Model.extend({
    onePost: belongsTo('post'),
    twoPost: belongsTo('post'),
    redPost: belongsTo('post'),
    bluePost: belongsTo('post')
});

App.Post = DS.Model.extend({
    comments: hasMany('comment', {
        inverse: 'redPost'
    })
});

I have zero clue why redPost is singled out here…I would think they all need the same type of relationship defined in this example. (Otherwise, why would you define them in the model?)

I’ve googled and stackoverflowed for real-life ember data inverse examples, but the only example/explanation I could find leads me to believe that inverse allows you to link the local attribute to a specific attribute in a foreign model. In the example provided, does that mean that the Post only implements one type of Comment for that application? And if you wanted more than one, you’d need to define inverse on all the attributes, but optionally for redPost?

App.Comment = DS.Model.extend({
    redPost: DS.belongsTo('post'),
    bluePost: DS.belongsTo('post', { inverse: 'blueComments' })
});

App.Post = DS.Model.extend({
    comments: DS.hasMany('comment', { inverse: 'redPost' }),
    blueComments: DS.hasMany('comment', { inverse: 'bluePost' })
});

I dig Dr. Seuss as much as the next dude, but I think his numbers and colors are confusing here. Maybe his Box, Thing 1 and Thing 2 would make a more enlightening example?


#8

Consider an blog application where there are posts and comments on posts and the comments could also contain links to other posts:

App = Ember.Application.create();

App.ApplicationAdapter = DS.FixtureAdapter;

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

App.Post.reopenClass({
  FIXTURES: [ { id: 1 }, { id: 2 }, { id: 3 } ]
});

App.Comment = DS.Model.extend({
    commentOn: DS.belongsTo('post'),
    linksTo: DS.hasMany('post')
});

App.Comment.reopenClass({
  FIXTURES: [
    { id: 1, commentOn: 1, linksTo: [] },
    { id: 2, commentOn: 1, linksTo: [2] },
    { id: 3, commentOn: 2, linksTo: [] },
    { id: 4, commentOn: 2, linksTo: [1] },
    { id: 5, commentOn: 2, linksTo: [1,2,3] }
  ]
});

Ember.Application.initializer({
  name: "TestPostComments",
 
  initialize: function(container, application) {
      application.deferReadiness()
 
      container.lookup('store:main').find('post', 2).then( function(post) {
        console.log( post.get( 'comments' ) );
        application.advanceReadiness();
      });
   }
});

There are no inverse relationships defined here and the fixtures define all the relationships in the comments only - so how does Ember know which comments the post.comments or post.linkedFrom hasMany relationships are linked to.

The short answer is that it doesn’t and will throw an error if you run the above code:

Error: Assertion Failed: You defined the 'comments' relationship on App.Post, but multiple possible inverse relationships of type App.Post were found on App.Comment. Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses

To solve this, you need to manually declare the inverse relationships:

App.Comment = DS.Model.extend({
    commentOn: DS.belongsTo('post',{inverse: 'comments'}),
    linksTo: DS.hasMany('post',{inverse: 'linkedFrom'})
});

Then the above code will run and you can get the comments on and linked to each post.