Series: Introduction to Ember Data 2.0

Ember Data 2.0-Metaprogramming with Relationships

Published on Jan 06, 2016

In this video we expand our completely generic model viewer by adding relationships. That means with just one template and controller, we’ll be able to see the relationships of all our models in both the abstract and the specific.

To do this, we’ll combine the techniques we learned while creating the generic model viewer in episode 101 with the things we learned about relationships in episode 104, while learning about the relationshipNames and relationshipsByName properties on DS.Model.


Links

Code

First we mash up the relationshipNames and relationshipsByName static properties on DS.Model in order to get an array usable in the template:

  //controllers/anything/model.js
  klass: Ember.computed.alias('model.modelObject.klass'),
  namedRelationships: Ember.computed('klass.relationshipNames', 'klass.relationshipsByName', function(){
    let relationshipNames = this.get("klass.relationshipNames.hasMany").concat(this.get('klass.relationshipNames.belongsTo'));
    return relationshipNames.map((relationshipName)=>{
      return this.get("klass.relationshipsByName").get(relationshipName)
    })
  })

Then we loop over that array and display the key, kind, and type:

{{! templates/anything/model.hbs}}
{{#each namedRelationships as |relationship|}}
  <br>
  {{relationship.key}}: {{relationship.kind}} {{relationship.type}}
{{/each}}

We ignore best practices and curse our descendants for seven generations by copying and pasting code into another class with only tiny edits (in the klass method):

  //controllers/anything/model/record/index.js
  klass: Ember.computed.alias('model.constructor'),
  namedRelationships: Ember.computed('klass.relationshipNames', 'klass.relationshipsByName', function(){
    let relationshipNames = this.get("klass.relationshipNames.hasMany").concat(this.get('klass.relationshipNames.belongsTo'));
    return relationshipNames.map((relationshipName)=>{
      return this.get("klass.relationshipsByName").get(relationshipName)
    })
  })

Then we use fancy nested gets to display the different types of relationships:

{{! templates/anything/model/record/index.hbs }}
{{#each namedRelationships as |relationship|}}
  {{relationship.key}}:
  {{#if (eq relationship.kind 'belongsTo')}}
    {{get (get model relationship.key) 'mainAttribute'}}
  {{else}}
  <ul>
    {{#each (get model relationship.key) as |oneOfMany|}}
      <li>{{oneOfMany.mainAttribute}}</li>
    {{/each}}
  </ul>
  {{/if}}
  <br>
{{/each}}

The mainAttribute attribute is added to the various models. This is an example from models/monster.js

  mainAttribute: Ember.computed.alias('name')

The full set of changes can be found on github.

Transcript

In Episode 101, we used metaprogramming and the attributes property on DS.Model in order to create this really cool app that will show us using the same templates and the same route and controller, the different properties for the different models that we have.

So here we’re seeing the monster, here we see all the properties on the monster, and then below for an individual monster we see the precise properties for that monster. But then we go to user and we can see the same thing.

So this is really cool, but there is one problem with it. We can’t see any of the relationships. And so that’s what we’re going to do in this video. We’re going to use the knowledge that we gained about Ember data relationships in Episode 104 and combine that with some of these properties on DS.Model.

So when I first started to try to put these relationships in here, I thought yeah, this is going to be easy. There’s the attributes property that we looped over before, and we already know how to get to it, so we can just use the relationships property.

But the relationships property is a little bit wonky. So instead of just giving us a list of relationships, you have to call .get on it, and then you pass it the klass. And so here for example, these two properties are based on the user model, and so you pass it App.User, or probably just do the string "user", and then it’ll give you this array.

So in order to access this, you have to already know what is already on there. And so for our metaprogramming, that’s not going to work, and these other relationship properties aren’t a whole lot better. You’ve got relationshipsByName, where you still have to know the name but now you’re knowing the name of the property rather than the name of the model that they’re connected with.

And we’ve also got relationshipNames, which it just gives you a list of names based on hasMany or belongsTo. Finally, we have the eachRelationship function, which seems to almost do what we want, but it requires a specific record, and that’s not exactly what we need. Finally we have eachRelatedType which gives us some stuff but not exactly what we need. But the good news is that by combining relationshipNames with relationshipsByName, we can cobble together something.

So we’ll define some stuff on our controller. First we’ll do... define klass with a ‘K’ as a convenience for accessing the klass constructor. And then, we’ll go ahead and create the namedRelationships computed property. This will rely on the relationshipNames and the relationshipsByName properties of our static klass. And then in our function we’ll get the relationshipNames. And remember that relationshipNames has been split into hasMany and belongsTo, so we’ll get both of those and concatenate them together.

So what we have here is a list of the property names, and what we’re going to do with them is we're going to map over them, and then we’re going to get the relationshipsByName hash and grab that relationshipName from there.

From there, we can loop over the each here, each of these namedRelationships that we found, and then we’ll be able to use the properties that we’re getting from the relationshipByName, and those are key, kind, and type.

Here we can see this displayed on our monster, and a similar thing is displayed on the team-membership and the user model. Now that we’ve got the relationshipTypes displaying, let’s go ahead and try to get the actual relationships for the individual records displaying.

Now don’t tell anyone that I did this, but I’m just going to copy this and put this into a new controller. Of course the model that we’re being passed is an actual model, so the klass will be the constructor rather than the thing we had before. Then we’ll loop through these relationships.

But we’re going to run into two problems. The first is that our hasMany and belongsTo relationships, we’re going to want to display those differently. And so we’ll solve that with a simple if statement.

The second problem is a bit trickier. To illustrate the problem, we’re going to do something naïve. We’re going to... on the model, we’ll get the relationship.key. And so this will... if we’re say finding a user on the team relationships, this will get us the user. And this gets us the user or the monster or whatever that property is. But it’s not very readable to our user. To fix that, we’re going to nest 'gets'.

So this is the actual model, the record that we’re getting from the relationship. And then we’ll go ahead and get the mainAttribute, and that’s something we just made up. So let’s go on user, and for them, the mainAttribute, it’s just going to be an alias to email. Meanwhile on our monster, the mainAttribute is going to be an alias to its name. And here we can see in our app it’s working beautifully.

For the hasMany, I’ll go ahead and paste my solution in here. So it’s a list, and this list is... you’ll recognize this from here. We’re getting the hasMany relationship and then we’re calling mainAttribute on each of those records. Team memberships are what we have many of in this application, and so we’ll give them a mainAttribute as well. And notice here that we’re combining the main attributes of the monster and the user, since there’s not really any distinguishing stuff about the team-membership itself. And we’ll look on the monster and grab one of these, and here is one of its memberships, and here is a user where we can see multiple team memberships.

So, this has been a little bit of a crazy dive where we looked really deeply at what we can do with relationshipNames and relationshipsByName. And we also looked at the relationships and eachRelationship, and saw why they wouldn’t meet our specific needs for this project.

So anyway this was really cool, and I hope you enjoyed it as much as I did. Pretty soon we’ll be editing some of this stuff, and once again of course it will be generic. Well, I haven’t actually made the code yet. I think it can happen, we’ll see. See you then.

Introduction to Ember Data 2.0

Subscribe to our mailing list