Well that was unexpected

I am no longer with GitHub. Working at GitHub was a pretty amazing experience. I enjoyed every single minute of it except for the last half hour or so.

However, impermanence is — so i am back on the market. If you need someone who knows a fair bit about web API design, SOA (of the REST flavor), Ruby and web applications check out my resume and ping me. I’d love to chat about opportunities.

Rails tip #72: hands off other’s private parts

In Ruby on Rails the most common way to pass data from the controller to the views is by allowing views direct access to the controller’s instance variables. Encapsulation is one of the cornerstones of software engineering. Why is it thrown out the window for views? Allowing external code access to an objects private parts is just wrong! Seriously, god kills a kitten every time someone does this. What is worse this anti-pattern is perpetrated in pretty much every tutorial on Rails Guides and every other RoR tutorial i have ever seen.

Forgoing encapsulation for controllers has the same issues as it has for any other type of object. You couple the implementations of the two components in a way that has very high connascence. This means that if either the view or the controller change it is very likely to require a change to the other. All of this adds up to more fragility and maintenance and less fun.

Consider this basic posts controller implemented in the current nominal RoR style


class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
  
  def new
    @post = Post.new
  end

  def show
    @post = Post.find params[:id]
  end
    
  def update
    @post = Post.find params[:id]
    @post.update_attributes params[:post]
  end
  
  def destroy
    @post = Post.find params[:id]
    @post.destroy
  end
end

That repetition around finding the post sets my teeth on edge. You could fix the repetition by pulling it out into a before filter but that complexifies the code and makes the already deep stack even deeper. The indirect invocation nature of filters makes it easy to overlook their existence and it requires you remember which actions they get invoked on and which ones they don’t. You even have to keep that stuff in mind when writing partials that are many levels of inclusion removed from the controller. Having to keep all that state in your brain slows you down. It also increases the risk of misremembering something and writing a view that doesn’t work.

Fortunately, Rails has a good solution to this problem. Consider the following refactor


class PostsController < ApplicationController
  def update
    post.update_attributes params[:post]
  end
  
  def destroy
    post.destroy
  end
  
  def post
    @post ||= if params[:id]
                Post.find params[:id]
              else
                Post.new
              end
  end
  helper_method :post
  
  def posts
    @posts ||= Post.all
  end
  helper_method :posts
end

And an accompanying view partial


<h1><%= post.title %></h1>
...

Now we have two efficiently memoized accessor methods for the data we want to expose to the views. The resulting code is better in several ways

  • The code is DRYer. If we want to implement visibility control for posts we can do it in exactly one place.
  • The logic of the action is easier to follow because the data lookup code is called explicitly.
  • The views and controller are less connascent. For example, if the show view wants to display a list of all posts in the side bar, only the view needs to change, not the controller.
  • The code is easier to keep efficient because those database lookups only happen if they are needed. There is little chance of looking up data that is not used by any one. The lookups only happen if the controller or views explicitly request the data.
  • The view code is more reusable. If you want to reuse that partial in another view (say by fully displaying the ten most recent posts in the index view) you can easily do so by rendering it with a post local variable.

Encapsulation is just as good a policy for controllers as it is for models.

HTML is domain specific

The partisans of generic media types sometimes hold up HTML as an example of how much can be accomplished without domain specific media types. HTML doesn’t have application/business specific semantics and the whole human facing web uses it, so machine clients should be able to use a generic media type too. There is just one flaw with this logic. HTML is domain specific in the extreme. HTML provides strong semantics for defining document oriented user interfaces. There is nothing generic about HTML.

In the HTML ecosystem, the generic format is SGML. Nobody uses SGML out of the box because it is too generic. Instead, various SGML applications, such as HTML, are created with the appropriate domain semantics to be useful. HTML would not have been very successful if it had just defined links via the a element (which is all you need to have hypermedia semantics) and left it up to individual web sites to define what various other elements meant.

The programs we use on the WWW almost exclusively use the strongly domain specific semantics of HTML. Browsers, for example, render HTML based to the screen based on the specified semantics. We have web readers which adapt HTML — which is fundamentally visually oriented — for use by the visually impaired. We have search engines which analyze link patterns and human readable text to provide good indexing. We have super smart browsers which can often fill in forms for us. They can do these things because of the clear, domain specific semantics of HTML.

Programs don’t, generally, try to drive the human facing web to accomplish specific application/business goals because the business semantics are hidden in the prose, lists and labels. Anyone who has tried is familiar with the fragility of web scraping. These semantics, and therefore any capabilities based on them, are unavailable to machine clients of the HTML based web because the media type does not specify those semantics. Media types which target machine clients should bear this in mind.