Join our team!

We're looking for mid-level and senior software developers who love working throughout the stack, and have a track record for designing, building, shipping, and supporting web services and applications. Ideal candidates love Ruby, Rails and Ember, and using BDD and agile processes to ship working software quickly! Experience with Redis, Postgres, RabbitMQ, and ElasticSearch are a huge plus!

Learn more about our mission by visiting IoraHealth.com and by following @IoraHealth.

You're Doing it Right: Using the NullObject Pattern in Views

Last week I talked about being more explicit with objects that you pass into views. Today I’m going to expand upon that thought by applying the NullObject pattern to said view objects. One of the joys of Backbone.js apps is that you can have a number of views active at the same time. The downside is that test setup can be difficult when you are trying to test a view in isolation that is responsible for initializing many child views.

One way around this is really using conditional logic in your views around any objects passed in:

TodoApp.Views.Users.Show = TodoApp.Views.CompositeView.extend({
  initialize: function(options) {
    this.user = options.user;
  },

  render: function() {
    if (user) {
      $(this.el).html(JST['users/show']({ user: this.user }));
    }
    return this;
  }
});

This seems reasonable when you’ve got one object, but quickly gets out of control:

TodoApp.Views.Users.Show = TodoApp.Views.CompositeView.extend({
  initialize: function(options) {
    this.user      = options.user;
    this.blogPosts = options.blogPosts;
    this.comments  = options.comments;
  },

  render: function() {
    if (user) {
      $(this.el).html(JST['users/show']({ user: this.user }));
    }
    if (blogPosts) {
      blogPosts.forEach(blogPost) {
        var child = new TodoApp.Views.BlogPost({ blogPost: blogPost });
        this.addChild(child);
      }
    }
    if (comments) {
      comments.forEach(comment) {
        var child = new TodoApp.Views.Comment({ comment: comment });
        this.addChild(child);
      }
    }
    return this;
  }
});

The end result is a lot of conditional logic and a render() method that we clearly need to refactor into multiple methods. Just to avoid having to setup the user, blogPosts, and comments objects when testing a view that calls show!

We can clean this up significantly like so:

TodoApp.Views.Users.Show = TodoApp.Views.CompositeView.extend({
  initialize: function(options) {
    this.user      = this._initUser(options.user);
    this.blogPosts = this._initblogPosts(options.blogPosts);
    this.comments  = this._initComments(options.comments);
  },

  render: function() {
    $(this.el).html(JST['users/show']({ user: this.user }));

    blogPosts.forEach(blogPost) {
      var child = new TodoApp.Views.BlogPost({ blogPost: blogPost });
      this.addChild(child);
    }
    comments.forEach(comment) {
      var child = new TodoApp.Views.Comment({ comment: comment });
      this.addChild(child);
    }
    return this;
  },

  _initUser: function(user) {
    return user ? user : new TodoApp.Models.User();
  },

  _initblogPosts: function(blogPosts) {
    return blogPosts ? blogPosts : new TodoApp.Collections.BlogPosts();
  },

  _initComments: function(comments) {
    return comments ? comments : new TodoApp.Collections.Comments();
  }
});

Now the render() method is without conditional logic! We have managed to shift the responsibility as to whether or not objects exist out of the render method, whose primary concern is to display the objects. Refactoring isn’t necessarily always about removing lines of code, but it is always about placing responsibility in the correct location.

This code now allows us to test a view containing this one without having to setup the three objects utilized in the view. Both examples accomplished that feat, but the developer who touches the code later would likely be confused as to why there is so much conditional logic inside of render() in the first example. The latter example clearly defines the intent of sane default object rendering.