Using Backbone.js Class Properties as Data Stores

Update: a Backbone plugin version of the following pattern can be found on the backbone-modelling GitHub repo.

I've been looking into ember.js recently and really liked the look of Ember Data: a library for loading and storing models from a persistence layer. It has a nice ActiveRecord-like API for querying models, fetching any models from the server that have not been created or stored locally.

It's something I've emulated (very basically) in previous Backbone.js projects, so it's nice to see it implemented so completely.

Inspired on the Ember Data API, I thought it may be worth improving my own implementation, making use of Backbone's class properties, like so:

window.Blog = {
  Models: {},
  Collections: {},
  Views: {},
  Routers: {}
};

Blog.Collections.Posts = Backbone.Collection.extend({
  model: Blog.Models.Post // this will be undefined
});

Blog.Models.Post = Backbone.Model.extend({
  initialize: function() {
    this.constructor.store.add(this);
  }
}, {
  store: new Blog.Collections.Posts()
});

First we set up the collection, referencing the Post model (Update: this will be undefined, causing potential problems down the line. See update below). Then we define the Post model itself, which includes a store (as a class property) and adds each instance to this store on initialize. It's worth noting that this method makes use of Backbone's extend function for Backbone classes (see annotated source code), which differs from underscore's extend function: instance properties are extended with the first parameter, class properties in the second.

This sets up a basic store, allowing us to do things like:

Blog.Models.Post.store.length; // 0
var post = new Blog.Models.Post();
Blog.Models.Post.store.length; // 1
Blog.Models.Post.store.first() == post; // true

Of course, all the Underscore collection methods are available on the store property. Fetching models that are not stored or created locally shouldn't be too hard to add: a feature for another blog post, perhaps…

Update: Adding the model property and memoizing the store

This implementation is ok, but it doesn't allow the Post model to be referenced in the store's collection. Not ideal.

To fix this we will include the model property when the collection is instantiated. This requires us to convert the store property to a function that returns the collection, memoizing the collection (in Blog.Model.Post._store) in the process:

Blog.Models.Post = Backbone.Model.extend({
  initialize: function() {
    this.constructor.store().add(this);
  }
}, {
  store: function() {
    return this._store = this._store ||
      new Blog.Collections.Posts(null, { model: this });
  }
});