Courtenay : August 23rd, 2007
The Presenter pattern, as my limited monkey-brain can see it, is a way of encapsulating a bunch of logic and display-related code for a database record.
If you want to be truly confused, go check out what the venerable Martin Fowler (who once ignored me in an elevator) has to say about it: Supervising Presenter and Passive View. As usual with Java people, it's horribly complex.
In Rails, this is the way our requests currently work:
The request comes in, hits the controller, we load up some data from the model, it gets pushed to the view, and then we use a combination of helpers and lots of conditional and other stuff that looks like PHP to malleate it until it looks good.
Unfortunately for my simple chimpanzee neurons, this all feels wrong. Here's some sample 'wrong' code I just paraphrased from a live app:
<% if @cart_item.variation.nil? or @cart_item.variation.product.deleted? or ... %> <img src="/images/active.png" /> This cart is no longer active. <% end %>
OK, this one is easy to refactor. You just add a method to
CartItem model, like
def is_active? variation.nil? or variation.product.deleted? ... end
Or, you could write a helper method:
module CartItemHelper def show_active(cart_item) image_tag("active") + " This cart is no longer active" if cart_item.is_active? end end
Ugh. Capital Ugh. First off, the is_active? method is nice enough, but it's all view logic! This method is being used to control the logic in the presentation layer. What is it doing in the model? I think of the model as entirely database related.
This is where the "presenter" comes in. If you've used the Liquid template/layout system, you'll be familiar with Drops. Basically, a presenter contains any ruby code related to displaying fields or logic. I'll let the code do the talking:
class CartItemPresenter < Caboose::Presenter def name h(@source.product_name) end def product_link if item.variation.nil? name else link_to name, fashion_product_path(item.product) end end def is_active? @source.variation && @source.variation.product end def inactive_button return if is_active? image_tag('active') + " This product is not active." end end
Pretty straightforward. In the controller, you finish with
@cart_item = CartItemPresenter.new(@cart_item, self)
Here's what Presenter looks like.
class Presenter include ActionView::Helpers::TagHelper # link_to include ActionView::Helpers::UrlHelper # url_for include ActionController::UrlWriter # named routes attr_accessor :controller, :source # so we can be lazy def initialize(source, controller) @source = source @controller = controller end alias :html_escape :h def html_escape(s) # I couldn't figure a better way to do this s.to_s.gsub(/&/, "&").gsub(/\"/, "\"").gsub(/>/, ">").gsub(/</, "<") #> end end
So, your view no longer accesses the database directly; everything goes through the presenter. Your views now will contain very little logic; in fact, they may start to feel a little more like ASP.Net.
Here's how the new stack looks:
And the view:
<%= @cart_item.product_link %> <%= @cart_item.inactive_button %>
What do you think? Is this layer of abstraction necessary, or do you prefer keeping things together in the model (database!) and presenter (view!) ?
P.s. I often write "todo" titles for articles and save them as drafts, so I can come back later. Seems like one slipped out.