Simple Model Relationships with Backbone.js
- 6/4/2012
- ·
- #index
Backbone brings an awful lot of tasty sugar to common app-development tasks, but the single most compelling argument for adopting it just might be how neatly it wraps up RESTful AJAX requests. Given a collection of models, all that’s needed to retrieve a server-side update is a single call to Collection.fetch()
:
var post = Backbone.Model.extend({});
var postCollection = Backbone.Collection.extend({
model: post,
url: '/posts'
});
posts = new postCollection();
posts.fetch(); // GET /posts
When /posts
points to a list of posts exposed by the API, Backbone’s fetch
method will return shortly with a corresponding list of models. No surprises there.
Things get a bit tricker when a request goes looking for a subset of the full collection. Instead of asking for all the posts on a site, maybe an application needs to grab only the posts that belong to a particular blog. The quick answer might be to modify the url function to retrieve the blog’s posts:
// Overly coupled model relationship. Don't do this:
postCollection = Backbone.Collection.extend({
model: post
url: function () { return '/blog/' + this.blogId + '/posts'; }
});
posts = new postCollection();
posts.fetch(); // GET /blog/3/posts
But what happens when a post can be related to more than one model? It doesn’t make sense to ask for all of the posts authored by a specific user from /blog/14/posts
. Rather, it would be much clearer to offer a new resource URL:
/users/me/posts
The trouble is that the URL function currently assigned by postCollection
knows nothing about /users
. This is the exact problem that relational models set out to solve. Paul Uithol’s backbone-relational library offers a comprehensive implementation of many familiar relationships, but for lightweight projects it might be worth considering how individual relationships may be defined.
Returning to the problem at hand, there are three obvious solutions for describing post relationships. In order of ascending practicality, they are:
- Create a convoluted helper for telling the
url
function where to look - Extend
postCollection
, redefiningurl
for each relationship - Create relational methods in the collection and adjust the
url
method accordingly
Extending the collection isn’t out of the question in certain situations (e.g., where child models will not belong to more than one parent), but the third choice is the one suggested in the documentation. Put into action, the “has-many” relationship between a blog and its posts turns out like this:
var postCollection = Backbone.Collection.extend({
model: post,
url: function () { return '/posts'; }
});
var blog = Backbone.Model.extend({
defaults: { posts: [] },
initialize: function () {
var self = this;
this.posts = new postCollection(this.get('posts'));
this.posts.url = function () {
return self.url() + '/posts';
};
},
urlRoot: '/blog'
});```
The magic here takes place when the collection is initialized. First, a new `postCollection` is drawn from whatever is stored in the model’s `posts` attribute. That makes it possible to pass in an array of posts directly to the `blog`—very handy for dealing with responses from a JSON API:
var data = { id: 42, posts:[ { id: 13, title: ‘hello, world’ } ] };
var myBlog = new blog(data);
The second step is to assign the posts’ URL so that requests referencing individual posts will be directed through the blog’s endpoint. Now, each post’s URL will be computed relative to the parent model
myBlog.posts.each(function (post) { console.log(post.url()); // /blog/42/posts/13 });```
The possibilities beyond this fairly trivial example aren’t hard to imagine. Mirroring the “has-many” posts relationship for a blog resource as a “belongs-to” relationship for each post would simply be a matter of creating a reverse reference in each post. But any more serious relationships are probably a hint that it’s time to seriously consider adopting a more serious Model library. No need to reinvent the wheel. For simple relationships, however, it only takes moments to adjust Backbone’s built-in Model
s to suit.
Related Links
- JS Fiddle example of the implementation shown here
- Backbone.associate on Github
- Backbone.associate—a tiny plugin implementation .