Give your Resources Context using render_with_context

Use convention to simplify your object partials by giving them context.

The Case for Context.

Introduced in rails 2.0, default partial rendering based on object naming conventions did quite a bit to clean up my view code. However, in most of the applications I’ve worked on objects need to be displayed differently depending on what context they are shown in. Take the example of rendering items on a dashboard, or with a minimal set of attributes in the sidebar. If you want to make use of the partial conventions, you end up throwing a bunch of conditional logic into the views to control what appears when.

I wanted to be able to specify which partial should be used to render the object from each view. Something like this:

render :partial => @items, :locals => { :context => :dashboard }

How it Works.

In keeping with convention over configuration, I decided to piggy back on what already exists in Rails. Each resource gets a new folder in it’s view directory called contexts. It looks like this:

Inside the contexts directory we see three separate context partials, _dashboard.html.haml, _default.html.haml, & _minimal.html.haml. Each one of them contains the markup for that object when displayed in a given context. The names of the contexts are derived from the filenames, so you can pass :dashboard or :minimal to the locals hash when rendering objects. The :default context gets used if there is no :context key in the locals hash.

The _item.html.haml partial contains only the call to render_with_context as so:

render_with_context(@items, :dashboard)

This method passes along the item to be rendered along with the local_assigns, which now include a :context key to be used in determining which partial to render. The render_with_context method lives in app/helpers/application_helper.rb and looks like this:

def render_with_context(resource, locals = {})
  context = locals[:context] ||= :default
  resource_root = "#{resource.class.to_s.pluralize.underscore}"
  partial_path = "#{resource_root}/contexts/#{context.to_s}"
  render :partial => partial_path, :object => resource, :locals => locals
end

The example at the beginning of this post would result in the _dashboard.html.haml partial being rendered. This partial looks like this:

%div[object]{ :class=> "#{cycle("odd", "even")} #{context}" }
  .thumbnail= image_tag object.preview(:mini)
  .title=h object.title
  .body=h object.body

If you were to pass :minimal to the :context local variable then the _minimal.html.haml partial would be rendered. Here it is:

%div[object]{ :class=> "#{cycle("odd", "even")} #{context}" }
  .thumbnail= image_tag(object.preview(:preview))
  .title=h object.title

Note that the body is no longer displayed, and the thumbnail now uses a different preview. This code isn’t cargo cult friendly as there are some conventions in here from the excellent make_resourcful plugin, and obviously all these partials are in the haml format, but you should get the idea.

Where it Could Go.

This is a rough first attempt at this concept. I don’t like the misdirection in the _item.html.haml partial, and setting up the render is cumbersome due to the locals hash. Ideally that will all be wrapped up into a new method so we can do something like:

render_with_context(@items, :dashboard)

Or with even more sugar:

render_on_dashboard(@items)

Recently Rails saw some commits which will enable something similar to this, only it is used to render different partials based on locales for l10n support. I suspect there might be some insights in that code on how to patch Rails itself with this feature. At the very least I would like to make this compatible with the new localization features, though I’m not quite sure what the best organization conventions would be in that case.

What Do You Think?

I would love to hear some feedback on this, positive or negative. How do you manage complex view requirements for your resources? Does this address a real problem, or is it just an unnecessary abstraction?