1 Comment

The Exceptional Presenter

PresentationObject is an implementation of Jay Fields’ Presenter pattern in Rails. It has familiar declarative style syntax and offers encapsulating presentation logic in a testable object.

Without a presenter-like object it’s easy to spread presentation logic and information across the MVC architecture: models end up with unneeded methods, controllers make unneeded decisions and view templates become corroded by unneeded code. Combined it becomes difficult to test and maintain presentation logic in the application.

Here’s an example of a simple presentation-oriented method existing in a model:

1
2
3
4
5
6
7
8
9
10
class Group < ActiveRecord::Base
  belongs_to :parent
  has_many :subgroups

  def show_relationships?
    return true if parent || subgroups.any?
    false
  end

end

The show_relationships? method is only used in a view to determine whether or not it should display the relationships information. It doesn’t really belong on the model.

Here’s an example of a controller making presentation decisions:

1
2
3
4
5
6
7
8
9
10
11
class GroupsController < ApplicationController

  def index
    if current_user.superuser?
      @groups = Group.find(:all)
    else
      @groups = Group.find_active_groups_with_member(current_user)
    end
  end

end

The index action introduces an unneeded branch of logic for a controller to have. It is painful to see this branch sprinkled throughout any controller that has to do with supplying @groups to the view because you will have to test the branch separately for each controller action that includes it. A common mistake made is to make this branch a before filter and define the method inside of the ApplicationController. Before filters can be easily misused and that is a topic for its own post so let’s not discuss that here—just know that using a before filter for this example is not the best thing to do.

Here’s an example of code that should not be in the view:

1
2
3
4
5
6
<%= Group::GROUPS_TITLE %>
<% Group.find(:all).each do |group| %>
  <% if group.member?(@current_user) %>
    <%= link_to h(group.name), group_path(group) %>
  <% end %>
<% end %>

The first thing to stand out is the Group.find(:all) call. It shouldn’t be there. The second thing to spot is the group.member? call. It doesn’t need to be there. Lastly, the usage of the GROUPS_TITLE constant doesn’t belong in the view.

Rather then sprinkling these presentation-oriented decisions and knowledge across the whole MVC architecture the PresentationObject drops in as an intermediary between the view and the model. It is responsible for knowing that the logic it contains will be presentation logic. This makes it the perfect place to put logic that otherwise shows up in the model, controller or view. A strength of the presenter is that it can mimic the model.

Here’s a GroupPresenter moving code out of the model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class GroupPresenter < PresentationObject
  delegate :id, :to_param, :name, :members, :to => :@group

  def initialize(options)
    @group = options.delete(:group)
  end

  declare :show_relationships? do
    return true if @group.parent || @group.subgroups.any?
    false
  end
end

# in a controller action that deals with a single group
def some_action
  group = Group.find(params[:id])
  @group = GroupPresenter.new group
end

By creating a GroupPresenter we’re able to keep our model responsible and disciplined. It also allows us to make little or no change to the view since the GroupPresenter delegates the necessary methods. Now, moving one method out may or may not be enough motivation for you to create a presenter. That depends on the situation you and your code are in. Unfortunately there’s rarely just one method that doesn’t belong on the model.

Here’s a GroupsPresenter moving code out of the controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class GroupsPresenter < PresentationObject
  include Enumerable

  def initialize(options={})
    @user = options.delete(:user)
  end

  declare :groups do
    if @user.superuser?
      Group.find(:all)
    else
      Group.find_active_groups_with_member(current_user)
    end
  end

  def each(&blk)
    groups.each(&blk)
  end

end


# in the index controller action
def index
  @groups = GroupsPresenter.new :user => current_user
end

Here the view won’t have to change. You gave it an array of groups before and you are giving it an object which acts exactly the same. The only difference is that your GroupsPresenter will make the right decision on what groups should be iterated over.

Before we look at a cleaned up view template there is more benefit with the PresentationObject and these last two examples.

First, it’s easily testable since all of the work is done in the methods themselves. Here’s a spec example covering the GroupPresenter we extracted from the model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
describe GroupPresenter, 'delegates' do
  before do
    @group = mock('group')
    @presenter = GroupPresenter.new :group => @group
  end

  it_delegates :id, :to_param, :name, :members, :on => :presenter, :to => :group 
end

describe GroupPresenter, 'show_relationships?' do
  before do
    @group = mock('Group')
    @presenter = GroupPresenter.new :group => @group
  end

  describe 'when the passed in :group has a parent' do
    it 'returns true' do
      @group.should_receive(:parent).and_return(true)
      @presenter.show_relationships?.should be_true
    end
  end

  describe 'when the passed in :group does not have a parent, but has subgroups' do
    before do
      @group.stub!(:parent).and_return(nil)
    end

    it 'returns true' do
      @group.should_receive(:subgroups).and_return([:subgroup])
      @presenter.show_relationships?.should be_true
    end
  end

  describe 'when the passed in :group does not have a parent or any subgroups' do
    before do
      @group.stub!(:parent).and_return(nil)
      @group.stub!(:subgroups).and_return([])
    end

    it 'returns false' do
      @presenter.show_relationships?.should be_false
    end
  end
end

The it_delegates spec helper is included at the bottom of this post.

Secondly, the presenter uses lazy evaluation and implements caching. For example the GroupsPresenter declares a :group method which retrieves all groups the user is able to see. This can be called as many times as you want, but it will only execute the first time. Every subsequent call will return a cached result. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
groups = GroupsPresenter.new :user => current_user
groups.each { || } # this will call :groups which will make a find call to Group
groups.each { || } # this will returned the cached value from the last time :groups was called

#likewise for the GroupPresenter
group = GroupPresenter.new :group => Group.find(:first)
group.show_relationships? # executes our implementing code
group.show_relationships? # returns the cached value from the last execution</li>

# this works for delegated methods to
group = GroupPresenter.new :group => Group.find(:first)
group.to_param # executed the delegated call to our group model
group.to_param # returns the cached value from the last execution

So depending on whatever your view ends up actually displaying the minimum number of calls to the database or to complicated methods will be made.

A real-world example of this is implementing a leader board which involves users, scores, games and user play regions. In order to show the leader board a number of computationally heavy algorithms and queries have to execute. These database and CPU intense calculations should happen once. PresentationObject gives you this advantage—for free.

Another advantage that is easy to overlook is where the decision is made that determines which specific query or computation to run. When it’s in a presenter it is isolated in an object which is only concerned with doing the right thing based on what needs to be displayed to the user. This makes it much simpler to comprehend and track down when compared to housing it inside one of the alternatives: the model and the controller.

But back to cleaning up the view template. Here’s what the view template should look like:

1
2
3
4
<%= @groups.title %>
<% @groups.each do |group| %>
  <%= link_to h(group.name), group_path(group) %>
<% end %>

We’ve pulled out two important things from the view template: the GROUPS_TITLE constant
and the conditional logic checking the current user for group membership.

Here’s the presenter that goes with the new view template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CurrentUserGroupsPresenter < PresentationObject
  include Enumerable

  def initialize(options)
    @user = options.delete(:user)
  end

  def each(&blk)
    groups.each(&blk)
  end

  declare :groups do
    Group.find_active_groups_with_member(@user)
  end

  declare :title do
    Group::GROUPS_TITLE
  end
end

# in the index controller action
def index
  @groups = CurrentUserGroupsPresenter.new :user => current_user
end

Pulling out the GROUPS_TITLE constant into a method on our presenter is important because the view template doesn’t need to know what title should be displayed. It just needs a title to display. If we wanted to build a title dynamically there is one single place where we can change that logic without affecting the view or anything else—the presenter.

Pulling out the conditional logic from the view template into our presenter gives us several advantages.

First, the view template is now available to be reused as a partial because it is only displaying the groups it is given. We can make the decision elsewhere on what groups to show and our template will just work.

Second, we’ve removed the urge to add any other conditional statements in that loop. We were just checking for group membership, but maybe we need to see if the user is a superuser. By placing this logic in the presenter we have removed the temptation of just adding more logic to the view template.

Lastly, we have created a single testable object for this presentation logic—our presenter. This greatly simplifies our view tests and the implementation of our views. And it stays consistent with placing this logic in a single object whose responsible for making these presentation-oriented decisions.

An alternative to this approach prior to the refactoring would have been to extract the code into a controller helper. This isn’t the right thing to do in our example. Helpers are too often used to tuck away code that shouldn’t be out in the presentation layer. They appear to add benefit because it’s so simple to move things from a view into a helper, but the long term debt far outweighs this short term gain. Helpers aren’t highly cohesive objects. Their responsibilities tend to be a little bit of everything and this hurts the ability to have simple testable objects that have highly cohesive behavior and responsibility. It also hurts maintainability. And it adds to the temptation that it is ok to put code in a helper that shouldn’t otherwise be there.

The name of this last presenter was CurrentUserGroupsPresenter whereas the earlier examples were GroupPresenter and GroupsPresenter. The goal of a presenter is to reduce the sprinkling of presentation logic throughout the system which in turn reduces complexity. We’ve found that presenters are cheap to create and work with. They usually work best when they focus on a single component in the UI. If we have four different components on the UI which each uniquely display groups on the site there may be four different group presenters, each one focusing on a specific UI component.

This differs from Jay Field’s presenter concept in that it focuses on encapsulating presentation logic rather then just encapsulating actions that operate on a model from the controller. This isn’t to say that these two ideas can’t be combined successfully or that one is better then the other. We have found that encapsulating presentation logic in a presenter is a great win overall and we haven’t had the need to push other responsibilities such as model creation and updates onto the presenter itself. This usually ends up in a manager-like object.

Installing PresentationObject

The current release is a 0.1.0 release:

1
2
3
4
5
# installing the latest presentation_object (points to 0.1.0)
script/plugin install svn://rubyforge.org/var/svn/atomicobjectrb/tags/presentation_object

# installing the 0.1.0 release specifically
script/plugin install svn://rubyforge.org/var/svn/atomicobjectrb/tags/presentation_object-0.1.0

it_delegates

An RSpec helper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Spec::Example::ExampleGroupMethods
  def it_delegates(*args)
    options = args.extract_options!
    methods = args
    src = options[:on]
    to = options[:to]
    raise "Missing an object to call the method on, pass in :on" unless src
    raise "Missing an object to ensure the method delegated to, pass in :to" unless to

    method_options = options.reject{|k,v| [:on, :to].include?(k)}
    methods.each {|m| method_options[m] = m}
    method_options.each do |source_method, to_method|
      it "delegates ##{source_method} to @#{to}.#{to_method}" do
        obj = instance_variable_get "@#{to}"
        _src = instance_variable_get "@#{src}"
        return_val = stub("return val")
        obj.should_receive(to_method).and_return(return_val)
        _src.send!(source_method).should == return_val
      end
    end
  end
end