One amazing ability of Ember Data is connecting your models together through relationships in such a way that you don’t have to know the nitty-gritty details.
In this episode we look at the hasMany
and belongsTo
methods, seeing how they can be combined and how to use their async
, inverse
, and defaultValue
options.
Links
Code
You can define relationships by calling DS.belongsTo
or DS.hasMany
or a DS.Model
. The first argument is a string giving the name of the model used in the relationship.
export default DS.Model.extend({
user: DS.belongsTo('user')
})
Ember Data can infer the model without the first argument if the property name matches to the class name:
export default DS.Model.extend({
monster: DS.belongsTo(), //infers 'monster'
teamMemberships: DS.hasMany(), //infers 'team-membership'
favoriteMonster: DS.belongsTo('monster') //requires specification
})
The second argument to both of these methods is a hash with three possible properties: async
, inverse
, and defaultValue
. They are all demonstrated in the following reflexive relationship:
export default DS.Model.extend({
parent: DS.belongsTo('state', {inverse: 'children', async: true}),
children: DS.hasMany('state', {defaultValue: [], inverse: 'parent', async: true})
})
Transcript
One of the really cool things about Ember Data is how it lets you connect your models with relationships. In this episode, we’ll go over the belongsTo
and hasMany
relationships.
So in your models, your DS models, you can define relationships just like you can attributes. So here, we’re defining the hasMany
relationship of team-membership
to the teamMemberships
property.
Then, in the team-membership
model, we’re having the monster
property belonging to the monster
model. And so, it’s going to be linking to one monster
model, and it will also be linking to one user. And then in the user
model, it’s going to be linking to many teamMemberships
.
Now what this lets you do is it lets you have these relationships without worrying a whole lot about the underlying how Ember Data is fetching it from the server. It lets you act almost as if the stuff is already there.
And then here you can like alright, let’s get the currentUser
, let’s get all the teamMemberships
on it, and you don’t have to worry as much about the underlying structure. I mean you do a little bit, because as you can see in this example, we’re checking to see if the memberships have already landed before we do the mapBy
. But if we reference these relationships in a template, then Ember would take care of that for us.
It’s worth noting that if the model names correspond to the property names, then you don’t actually have to state the model name as an argument. You can just leave it blank. However, if the model name doesn’t correspond with the property name, you do need to have the model name as the first argument.
For example if we had favoriteMonster
on the user, then it would be a belongsTo
, and we would have to specify that it’s a monster
, since there’s no favoriteMonster
model.
There’s also some terminology that you should know that people use when talking about relationships. So here we have a team member relationship and we have many team members. And in team members well the team-membership
belongs to one user. We call that a one-to-many relationship. You can also have a one-to-one relationship by having two models that have two belongs-to
relationships that connect. And you can also have a many-to-many relationship by having two models that have hasMany
relationships that connect.
So for both belongsTo
and hasMany
, there are two optional arguments that you can put into the second parameter. So first you have the name then you have the optional arguments. The first optional argument is async
, and that lets Ember Data know whether this relationship is going to be synchronous or asynchronous.
Now it was false by default before Ember Data 2.0, but now in Ember Data 2.0, it’s true by default, and you have to manually turn it off if you want it to be purely synchronous. And in general, you’re going to want it to be async
.
Let me show you what synchronous versus asynchronous means. But before we do that, we’ll have to get into looking at this payload. So this is using the active model adapter, but the general lesson that I’m going to show you should carry over.
So here in this monsters call, it gives us both monsters
and team_memberships
. And here in the monsters
, in the team_membership_ids
property, it gives us an array of team membership ids. And for this, this could be both synchronous and asynchronous, because we’re including the team_memberships
.
However, with the team_memberships
, notice we have the monster _id
and the user_id
. These are both asynchronous by default, but it would be okay for the monster to be synchronous because this monster is included. But if we had the user as synchronous, then that would cause a problem because we haven’t included any users in this payload. So if it’s synchronous, it has to be included in the payload. If it’s asynchronous, then it can be fetched whenever you need it, not necessarily in the same payload.
So that’s the async
option. Now let’s talk about the inverse
option. So the inverse
option is useful if you have two models that have multiple types of relationships to each other. So the example they give in the documentation is that in the comments... a comment
can have different relationships to different posts. And then the post, it wants all the comments of a certain type, so it only wants the comments where this post is their redPost
. This example is a little bit nonsensical, but hopefully you can get the idea.
Let’s look at a possibly more useful example from our model state explorer. So here on our state
model, we have the parent
relationship and the children
relationship. So the parent, it belongsTo
another state
, and the inverse
is children
. And for the children
, it hasMany
states and the inverse
is parent
. So it’s not just one model related to another model, it’s one model related to itself. So this helps us keep track of how they’re related.
Now notice that we also have a third thing that wasn’t in the documentation, defaultValue
. And that’ll just set what it is before you get anything back, or if it’s null.
So that’s our exploration of the belongsTo
and hasMany
methods, the relationships in Ember Data. In this week’s pro-episode, we’ll be going over more of this code that I used to create the model state demonstration. It was introduced a couple of episodes back and now I’m going to show you some of the cool things I did while making it. I hope to see you then.
- Ember Data 2.0-Getting Started, and Basics of DS.Model
- Ember Data 2.0-Getting Data from the Server with findRecord and findAll
- Ember Data 2.0-Store Manipulation with Peek, Unload, and More
- Ember Data 2.0-Create, Save, and Destroy Records
- Ember Data 2.0-Updating Data, Tracking Changes, and Rolling Them Back
- Ember Data 2.0-Metaprogramming with DS.Model Attributes Property
- Ember Data 2.0-Model States and Flags
- Ember Data 2.0-states.js Deep Dive
- Ember Data 2.0-Relationships
- Ember Data 2.0-Metaprogramming with Relationships
- Ember Data 2.0-Overview of using Adapters and Serializers
- Ember Data 2.0-Overview of Customizing Adapters and Serializers
- Ember Data 2.0-Essential Adapter Customizations
- Ember Data 2.0-Advanced Adapter Customizations
- Ember Data 2.0-Miscellaneous Adapter Customizations
- RESTAdapter vs JSONAPIAdapter vs ActiveModelAdapter
- Introduction to Serializers
- JSON API
- Serializers-normalizeResponse
- Serializers-normalize${specific}Response
- Serializers-How are normalize and normalizeResponse different?
- Serializers-Extracting Attributes and IDs
- Serializers-Extracting Relationships
- Serializers-keyForAttribute and keyForRelationship