Fetching models with two belongsTo constraints

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!

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!

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.

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.

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.

1 Like

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.

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.