Between Controller and View

Jay Fields has been blogging about using presenters in Rails. I came across an example in a website I am working on which clearly demonstrates their usefulness.

If context helps, this is for a photography website.
  def review
    @order_items = current_order.order_items.find(:all)
    @items_by_image = @order_items.group_by(&:image_id)
    @images = Image.find(@items_by_image.keys)
    @applied_packages = current_order.best_available_packages
    @available_packages = current_order.event.available_packages.find(:all) - @applied_packages
  end

That controller action may look fairly normal; I'm simply setting some instance variables for use in the view. What's the problem?

The first problem is that this action has too much noise. Although five lines is not many, all I am doing is loading some data to present in the view. Sure, I could reduce this by letting the view call current_order.best_available_packages instead of assigning that to an instance variable, but that doesn't seem right either. This code needs to go somewhere else.

The second problem is with testing. To test that controller action with mocks, I need to mock/stub quite a bit. Also, testing one small piece of functionality is difficult. Because all five instance variables are set in one method, I cannot, for example, only test the @available_packages assignment (at least not easily).

I haven't actually made this into a presenter yet, but if you're wondering what it might look like:

class OrdersController < ActionController::Base
  def review
    @presenter = OrderReviewPresenter.new(current_order)
  end
end
class OrderReviewPresenter
  def initialize(order)
    @order = order
  end
  def order_items
    @order.order_items.find(:all)
  end
  def items_by_image
    @order_items.group_by(&:image_id)
  end
  def images
    Image.find(items_by_image.keys)
  end
  def applied_packages
    current_order.best_available_packages
  end
  def available_packages
    current_order.event.available_packages.find(:all) - applied_packages
  end
end

This solves both of my problems. It's so much easier to test as a presenter, and look at how skinny that controller is!

In addition to this example of a presenter for simply displaying data, presenters have other uses too. Read Jay's blog post for more information.

Updates

Steven Wisener gave me feedback that he thinks presenters are solely part of the view. I agree. Even though the controller is instantiating the presenter and the view is using it, the logic that goes into presenters is all view logic. Therefore presenters aren't really between the controller and the view, they're simply part of the view.