Problematic Helpers
There is the concensus that the View has to be free of logic. However there are different approaches, how to achieve that. Ruby on Rails provides Helper for that purpose.
Though Helpers are problematic quite often, because they are available in all Views by default. That means, all Helper share the entire name space.
The difficulty is obvious:
module ArticlesHelper
def title text
return if text.blank?
content_tag(:h3, text)
end
end
module ProductsHelper
def title product
link_to product.name, product, class: 'title'
end
end
The Helper naming (title) is not enough specific. The more Helper methods, the more doubled method names are likely to exist in the name space.
Besides Helpers tend to be cumbersome to test.
Presenter definition
The Presenter pattern is a Decorator pattern specialization. Objects are not only decorated, but also presented in the View context. That said, a Presenter object contains the View context (for the Rails Helpers) apart from the decorated object itself. It acts as a middle-man conveying between View and object.
On the one hand logic can be encapsulated specific to the object and on the other hand it also can be tied to a certain context.
In either case the functionality encapsulation simplifies isolated testing.
A problematic View example
A product model:
$ rails g model Product name:string && rake db:migrate
In the View the name has to be displayed different, depending on whether the product is new or not.
Nested logic in the View:
# app/views/products/show.html.haml
- if @product.in_range_at < 1.month.ago
%h3.new= @product.name
-else
%h3= @product.name
likewise the short form:
# app/views/products/show.html.haml
%h3{ class: ('new' if @product.in_range_at < 1.month.ago) }= @product.name
are unacceptable.
A Presenter solution
A simple Presenter class, combining the object with the View for inheritance:
class SimplePresenter
attr_reader :model
def initialize model, view
@model = model
@view = view
end
private
def h
@view
end
end
The ProductPresenter benefits from that and includes the appropriate implementation of the headline component:
class ProductPresenter < SimplePresenter
NEW_RANGE = 1.month
def name_tag
css = 'new' if new?
h.content_tag(:h3, model.name, class: css)
end
private
def new?
model.in_range_at < NEW_RANGE.ago
end
end
and its usage:
# app/views/products/show.html.haml
= ProductPresenter.new(@product).name_tag
At first glance the Presenter implementation looks extensive. Adopting the Presenter pattern evolves its full effect over more complex components and comprehensive projects.
Reasonable Helpers
Of course Helper make sense for View logic, which is required application wide. This includes not only Rails Helper (e.g. FormTagHelper), but also application specific Helper. Therefore building a Presenter instance should take place in a Helper:
# helpers/application_helper.rb
module ApplicationHelper
def present(model, presenter_class=nil)
presenter_class ||= "#{model.class}Presenter".constantize
presenter_class.new(model, self)
end
end
The object presentation in the View is simplified by that:
# app/views/products/show.html.haml
= present(@product).name_tag