What are the responsibilities of a Presenter?
The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view
or briefly
transform data from one to another form(representation)
I like to think of Presenter as just a transformation function withing a context(an MVC).
Thinking with mapping
Map is a very simple but powerful concept. Rubyists (and other functional style ppl) use it often to transform data:
[1, 2, 3].map {|e| '!' * e }
=> ['!', '!!', '!!!']
But how it relates to Presenter?
Well, Presenter is a Map too.
Let’s take an example from rails tips
class UserPresenter
def initialize(user)
@user = user
end
def as_json()
{
'id' => @user.id,
'email' => @user.email,
'name' => @user.name,
'first_name' => @user.first_name,
'last_name' => @user.last_name,
'urls' => {
'self' => "#{Gauges.api_url}/me",
'gauges' => "#{Gauges.api_url}/gauges",
'clients' => "#{Gauges.api_url}/clients",
}
}
end
end
and used like this:
get('/me') do
content_type(:json)
{:user => UserPresenter.new(current_user)}.to_json
end
rewriting it Map-style would look like:
UserPresenter = lambda do |user|
{
'id' => user.id,
'email' => user.email,
'name' => user.name,
'first_name' => user.first_name,
'last_name' => user.last_name,
'urls' => {
'self' => "#{Gauges.api_url}/me",
'gauges' => "#{Gauges.api_url}/gauges",
'clients' => "#{Gauges.api_url}/clients",
}
}
end
and use:
get('/me') do
content_type(:json)
{:user => UserPresenter[current_user]}.to_json
end
Big deal you say?!
Well lets use it for bunch of users
get('/all') do
content_type(:json)
{:users => User.all.map(&UserPresenter) }.to_json
end
nicer than:
get('/all') do
content_type(:json)
{:users => User.all.map {|u| UserPresenter.new(u).as_json } }.to_json
end
less code #FTW ( Of course you can tweak original class to act as Proc, but it requires more code )
Single responsibility and re-usability
Thinking with maps means having reusable “functions” with predefined interface
UrlsPresenter = lambda do
{
'self' => "#{Gauges.api_url}/me",
'gauges' => "#{Gauges.api_url}/gauges",
'clients' => "#{Gauges.api_url}/clients",
}
end
UserPresenter = lambda do |user|
{
'id' => user.id,
'email' => user.email,
'name' => user.name,
'urls' => UrlsPresenter.call
}
end
is also a good example of Single Responsibility
Map as a Decorator
EmailFilter = lambda do |data|
data.except(:email)
end
EmailFilter[UserPresenter[current_user]]
Map is a bidirectional concept
Along with converting output streams maps can be used to convert input streams(relative to an app’s point of view), which makes it not be a Presenter anymore:
xml = Nokogiri::XML(external_response)
# ...
XmlLineItemMap = lambda do |li_xml|
#...
end
XmlOrderMap = lambda do |order_xml|
{
:order_id => order_xml.at_css('order order_id').text,
:order_number => order_xml.at_css('order order_number').text,
:order_status => order_xml.at_css('order order_status').text,
:total_price => order_xml.at_css('order total_price').text,
:line_items => order_xml.at_css('order line_items').map(&XmlLineItemMap)
}
end
Pros and Cons
Pros
- Simplicity
- Single Responsibility
- Unified interface
- Reusability
Also
- Less code: constructor was just 3 useless lines
- Works well on “separation boundaries” when it’s neccessary to turn objects into PORO or other way around
Cons
- Eagerness
Usage
I’ve been using to avoid all the json building DSLs
Note to self
Software design patterns are about responsibilities. So don’t take patterns literally. And think in interfaces.