darthdeus' blog

Random thoughts about the world of programming

How to Find a Model by Any Attribute in Ember.js

One of the common things people ask about Ember Data is how to find a single record by it’s attribute. This is because the current revision (11) only offers three methods of fetching records

1
2
3
App.User.find(1) // returns a single user record
App.User.find({ username: "wycats" }) // returns a ManyArray
App.User.findQuery({ username: "wycats" }) // same as the above

If you want to search for a user by his username, you have two options

Using .find with smart server side

The way App.User.find(1) works is that it does a request to /users/1, which is expected to return just one record.

You could modify your server to accept both username and id on the /users/1 path, which would allow to do App.User.find("wycats").

There’s an issue with this though. If you load the same user via his username and id, you’ll end up with two records stored in the Ember identity map.

Which basically means that if you try to retrieve all of the user records, you will end up with that one user twice.

If you want to read more about this, checkout this GitHub issue

Using a findQuery

This might not seem like the right solution at first, since it returns a DS.ManyArray instead of just one record, but hang on.

DS.ManyArray is a subclass of DS.RecordArray, which includes a DS.LoadPromise.

To understand how DS.LoadPromise works, we need to understand what promises are. There’s a great article about that, so I won’t go into much detail.

Promise is basically an async monad (I guess that doesn’t help, let’s try again).

Promise is something which allows you to return an object which wraps around a value, even if you don’t have the value yet. For example if you’re doing App.User.findQuery, you’ll get back an empty DS.ManyArray instantly.

It doesn’t wait until the AJAX request is finished, it just returns the empty array, which is populated with the data once the request finishes.

This works because Ember uses data bindings and will automagically update all of the views once the data is loaded. And also because the router will wait if it’s model has a state isLoading. That way you won’t display a page which is half loaded.

Implementation

Now that we know we’re getting a DS.ManyArray, we need to figure out a way to make it represent only the value of it’s first element, because that’s what we care about.

1
2
3
4
5
6
7
var users = App.User.findQuery({ username: username });

users.one("didLoad", function() {
  users.resolve(users.get("firstObject"));
});

return users;

You can see that we are returning the result of the findQuery instantly, but we’re also setting an asynchronous callback which resolves the promise to the firstObject once it is loaded.

Another way you could read the resolve(x) is from now you’re representing value x. Using this technique will work in all Ember, because the data bindings will take care of everything. Always remember that you don’t need to worry about re-rendering your views, just change the data and Ember will take care of the rest.