The presenter pattern is one possible way to move logic from your views into a place where it's not only better suited, but can also be reused. Maybe that's the reason it's an essential part of the MVP pattern in other web frameworks. In this article, I want to offer a lightweight implementation that both explains some specialties when used with Rails helpers and doesn't need any external dependencies.

You've probably already heard or even used a gem like Draper, which introduces the presenter pattern to your Rails application. Note that there's some discussion about the difference between decorators and presenters. Strongly considered, the naming Draper adds is a bit off (see this discussion in their Github issues): Draper is a gem for presenters, not decorators. However, one can argue, that a presenter is just a more specialized form of the decorator pattern. And, most important, in the end it doesn't matter. As long as everybody in your team knows where to put things, you could also call presenters something else, like stones.

Thoughtbot has a nice post up on their Robots blog, which gives an excellent overview about the whys and hows of the presenter pattern. I encourage you to check it out, and, if you haven't already done so, subscribe to their blog updates, as they are a very educating read, every time.

The only shortcoming their suggested implementation has is that it's not taking into account integration with Rails' helper methods, like link_to, or asset_path. That's where things get interesting. Consider the following, naive implementation of the presenter pattern:

class SettingPresenter < SimpleDelegator
  def initialize(setting)
    super(setting)
  end

  def status
    return 'Not status' if super.nil?
    super
  end
end

What we're using here is the SimpleDelegator class from Ruby's standard library. SimpleDelegator will just forward all calls to the underlying object (here setting) when they can't be found within the actual object. Please note that the check for nil in the SettingPresenter#status method can and should be exchanged for another pattern: the Null Object pattern. But let's keep that example for now. Let's move on and include a link method into our SettingPresenter:

class SettingPresenter < SimpleDelegator
  include ActionView::Helpers::UrlHelper

  def initialize(setting)
    super(setting)
  end

  def link
    link_to 'Settings', settings_path(self)
  end
end

We've included the ActionView::Helpers::UrlHelper module because that one defines the link_to method, which we are using to generate the link to your settings page.

Depending on the complexity of your Rails project, the SettingPresenter#link method might or might not create a correct link to your settings page. Why? Because we don't have a reference to our current view context, the link_to method will try its best to come up with a valid host, port and other parts of the URL and configuration. But if you're e.g. dynamically changing the subdomain you're linking to from your ApplicationController based on the username, you will certainly be out of luck, and the link will point into Nirvana. Another set of common problems I've discovered are assets and digests. Everything will work fine in development but will break heavily in your staging, or even worse, production environment.

There's a very easy fix for this: By using the current view_context you can call link_to and other view helpers on that view context and they will correctly invoke Rail's helpers. If you had a look into Draper before, you would now understand why you need to utilize the h object to access Rails helper functions.

class SettingPresenter < SimpleDelegator
  def initialize(setting, view)
    super(setting)
    @view = view
  end

  def link
    @view.link_to 'Settings', @view.settings_path(self)
  end
end

# Usage
class SettingsController < ApplicationController
  def show
    setting = Setting.find_by_id(params[:id])
    @setting = SettingPresenter.new(setting, view_context)
  end
end

Because the view_context object already includes all the Rails helpers, there's also no need to include them in the presenter class anymore. One could still argue if it's okay to initialize the presenter in the controller or if the view wouldn't be a better place for this. Our implementation will support both cases - the view_context is available in both.

And voilà, we have rolled our own implementation of a presenter in 10 lines of Ruby code and now also revealed some of the magic of the Draper gem.

Changes

  • Jeroen suggested using super instead of __getobj__ to access the status of the underlying setting object within the first example. I've changed that part, as that makes it way cleaner to read. Thanks!