Backbone.js Patterns Pt.1: Maintaining the Uniform Access Principle

The entire [Backbone.js] source is annotated with the explicit idea that you should be feeling free, if something is not working, to dig in and figure out how it works and also to dig in and overwrite things if you find the need. Jeremy Ashkenas JavaScript Jabber 004

I thought I'd share a few patches that I've found useful when developing web apps with Backbone.js. The techniques overwrite prototype methods, but in a responsible way that keeps default behaviours unchanged.

This part takes pointers from this Stack Overflow question and the Uniform Access Principle:

All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation

Bertrand Meyer

Lets say you have a user model as follows:

App = {};
App.User = Backbone.Model.extend({
  defaults: {
    firstName: 'Jon',
    lastName: 'Snow'
  },

  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }
});
var user = new App.User();

fullName could be accessed by calling user.fullName(), but that would be inconsistent with accessing other attributes. What’s more, if, for example, we decided to compute fullName on the server, we'd have to replace all occurrences of user.fullName() with user.get('fullName').

It would be better to access fullName (and any attribute-like method) via user.get('fullName'). This can be achieved by overwriting Backbone.Model.prototype.get:

(function() {
  var oldGet = Backbone.Model.prototype.get;
  Backbone.Model.prototype.get = function(attr) {
    if(typeof this[attr] === 'function') {
      return this[attr]();
    }
    return oldGet.apply(this, arguments);
  };
})();

This stores the original get method in oldGet, calls the requested method if it exists on the model, or calls the original get method if it doesn't.

Backbone.Model.prototype.escape retrieves attributes by making a call to Backbone.Model.prototype.get, and so should also work with this technique.

Related: computed properties in Ember.js.

Feedback welcome via Twitter, or email.

Part 2 will look at techniques for customising toJSON and parse for synchronising data with the server.