Cleaner View Rendering in Rails
Published February 11, 2015
We've been using the "Presenter pattern" atClearSight for some time now. Since everyone seems to have gotten used to the idea of only using one instance variable per controller action, we decided to take it one step further.
We avoid convenience methods such as current_user
in the view and provide all data through the presenter in simple data structures rather than full ActiveRecord objects. It makes mocking and testing much easier if there isn't a reliance on exterior state or objects.
We also want to avoid setting any instance variables at all and instead provide a local variable via the locals
hash.
Here's how we've accomplished it through POROs (plain old Ruby objects). We're pretty happy with this and excited about how clean it makes our code.
The Controller
To clean up the render action (no ugly locals
hash), we provide a class method called build
that returns the right hash:
class MyController < ApplicationController
def index
render :index, MyIndexPresenter.build({
people: User.all,
current_user: current_user,
})
end
end
The Presenter
Each presenter is something like this:
class MyIndexPresenter < AppPresenter
def people
properties[:people].map do |person|
{
profile_url: "/user/#{person.id}",
display_name: "#{person.first_name} #{person.last_name}",
}
end
end
def current_user_name
properties[:current_user].display_name
end
end
Simple, direct, to the point. It exposes some convenient methods we can use in our view, referring to the properties
hash which is set on initialization. It explicitly provides hashes for any collections (like this group of users above), giving the view only the data it needs in the format it needs.
The View
We use Slim, and our view might look something like this:
header title = presenter.current_user_name div ul - presenter.people.each do |person| li = link_to person[:display_name], person[:profile_url] `}
The presenter
local variable has exposed two methods to us, so we use those. In most cases, these are hashes or strings or integers -- no complex objects at all.
The AppPresenter
The AppPresenter
is a PORO class that gives you just a few methods.
class AppPresenter
def self.build(args={})
new(args).render_options
end
def initialize(args={})
@properties = args
end
def properties
@properties ||= {}
end
def render_options
{ locals: { presenter: self } }
end
end
The build
method just conveniently callsnew(args).render_options
for us, so we can render cleanly in our controller (see above). It hangs onto properties that you pass into it, and the render_options
method passes back to Rails's built-in render
method the option hash it expects; mainly the locals:
directive which tells it to expose the presenter to the view.
What do you think of this pattern? Pros, cons? Rails developers never have opinions about this stuff, right? ;-) Let me know on Twitter!