Fetching models with two belongsTo constraints


#1

I was wondering if I could get some design help for my ember app.

Here are my three (partial) models:

Payment

  • belongsTo Group
  • belongsTo User

Group

  • hasMany Payments
  • hasMany Users

User

  • hasMany Payments
  • belongsTo Group

I would like to make a table that lists each user in the group and the payment for each user that coordinates with the group. I am currently using links with ember-data. Each user of the group has at most one payment for that group/user combination. I was wondering what the best way to get that payment would be.

I currently use the link groups/:id/users to get the users for the group. If I wanted to get the payment that belongs to both the user and the group would a link like groups/:group_id/users/:user_id/payment be an acceptable option? Is there a better way?

Thanks for the help!


#2

In my (limited) experience with Ember Data, you’ll save yourself some heartache if you do your sorting/filtering/organizing on the client.

/payments
/groups
/users
/payments/:id
/groups/:id
/users/:id

Once you have all the relevant records in the store, you can use find() to display them how you want, eg:

this.store.find(“payments”, {/some query here/});

Since you’ve defined the relationships in your model already, you can query the store in a manner similar to a database. I know it’s not a very rich REST API, and you can write your own adapter that works the way you’re describing, but this will work out of the box and give you a little more flexibility.

Hope this helps!


#3

Cool thanks Kyle!

I think I will have a computed property in the user controller (which needs group controller) that uses @store.find(‘payment’, { user_id: X, group_id: Y }) that is bound by user_id and group_id.

Is there any difference between @store.findQuery and@store.find for this type of lookup? I assume that neither method will search the cache (if given an object as params) but I’m not to worried about that initially.


#4

find() overloads based on what you pass into it.

find("someModel") // Returns all records
find("someModel", 1) // Returns a single record because you passed in an integer
find("someModel", {name: "Steve"}) // Runs a query because you passed in an object

find() wraps the find(), findAll(), and findQuery() methods into one rad super-method.

When you search the store, you’re always searching the cache first. It only sends off a request when it doesn’t find something it needs.

http://emberjs.com/images/guides/models/finding-unloaded-record-step1-diagram.png

Just a note on “needs”- you’re often better off getting data from another controller (or searching the store directly) via the setupController hook on the route. When you “need” a controller, you’re capturing its state once, the first time the controller is initialized. The setupController hook fires every time the route is accessed. IMO, this is the more common requirement- “needs” has a place, but it’s been more of a corner-case for my stuff.


#5

Nice. Thanks again Kyle!

I have a quick questions about needs. Right now to build my table, I loop through each group.users and use the UserController to form all the fields I need (for example Payment like we talked about earlier). For the UserController to know the current Group, I set the model for the GroupController and then the UserController needs the GroupController and therefore I know the Group. Is this the correct approach? It currently works but I would like to do things by the book.


#6

The book is still being written, and I’m pretty fresh at this myself, but here’s my take:

I really only get a lot of use out of the model hook if the template is describing a single, simple thing. Otherwise, I’ll do something like this:

App.AdminRoute = Ember.Route.extend({
  setupController: function(controller, model){
    this.store.find("image").then(function(images){
      controller.set("images", images);
    });
    this.store.find("pitch").then(function(pitches){
      controller.set("pitches", pitches);
    });
    this.store.find("contact").then(function(contacts){
      controller.set("contacts", contacts);
    });
  actions: {
    saveRecord: function(record){
      record.save();
    }
  }
});

You can then refer to these in templates like you would if they were on the model or directly on the controller:

{{#each pitches}}
  {{name}}
{{/each}}

It’s a little bit more DRY to refer to other route’s models and controllers (which you can even still do with this method), but IMO this offers a little more code clarity and a little less automagic while still maintaining good separation of concerns.