We have been using presenters much more often in our Rails code, with the goal of having views only use “Mustache-level” logic (conditionals, loops, and interpolation). We’ve been happy with the results so far; it really cleans up the views, and moves logic to easily testable objects. In fact, most tests for presenters can be fast specs and still test well.

I’ve gotten the impression that not everyone does presenters the same way, so before getting to the code I’ll clarify. Our presenters are decorator-like, with a restriction that they only transform values, not make any lasting changes to them. Methods are typically “opted-in” for use in the presenter using def_delegators from Forwardable. For a simple example, PersonPresenter might have a method called full_name that combines first_name and last_name; the Person class only manages the persistance of first_name and last_name. That is, presenters wrap an object with methods for presentation to a human.

Not all of Rails makes this pattern easy to use. We ran into an issue recently when calling dom_id() in a view. This was our code, roughly:

# File: app/presenters/foo_presenter.rb
class FooPresenter
  def initialize(foo)
    @foo = foo
  end

  def bar
    # ...
  end

  # ...
end

# File: app/controllers/foos_controller.rb
class FoosController < ApplicationController
  def show
    foo = Foo.find(params[:id])
    # It's nice to only have to expose a single object to the view.
    @foo_presenter = FooPresenter.new(foo)
  end

  # ...
end
# File: app/views/foos/show.html.erb
<span id="<%= dom_id(@foo_presenter) %>"><%= @foo_presenter.bar %></span>

And this is the error we got:

ActionView::Template::Error (undefined method `to_key' for #<FooPresenter:0x007fc782994a98>):
1: <span id="<%= dom_id(foo_presenter) %>"><%= foo_presenter.bar %></span>
[...]

The obvious fix would be to add a to_key method that just delegates to the model, but that quickly became a rabbit hole. Many more methods were needed, and I quickly gave up.

It turns out that dom_id() follows a protocol in that it will call to_model on whatever’s passed in. We can use that to solve our problem. (ActiveRecord objects just return self, as you might imagine.) Here’s the fix:

# File: app/presenters/foo_presenter.rb
def FooPresenter
  # ... (previous content)

  def to_model
    @foo
  end
end

Now the presenter can be used with dom_id(), delegating to the presented model. It’s a surprisingly simple solution. :)

(Versions: Ruby 2.0.0, Rails 3.2.12)