DigitalOpera::Presenter::Base - noiseunion/do-toolbox GitHub Wiki
So the interwebs are full of articles and opinions on how much Rails helpers are not exactly the BEST way to handle presentational changes to our data models. Well, we agree.
In the past we found ourselves putting various chunks of presentational logic in either helpers, or even worse yet...our models. After learning our lesson, we started adopting "Presenters". Presenters use the Decorator Pattern to wrap our models with additional features that pertain specifically to data presentation.
So lets get to our implementation, and you'll see what we mean.
Create a Presenter
In a Rails application, we like to put these under app/presenters
. Just create the presenters directory and it will be auto-loaded into your Rails app. No hassle!
The Model
# app/models/user.rb
class User < ActiveRecord::Base
# This model has two fields: first_name and last_name - you know the drill.
end
The Presenter
# app/presenters/user_persenter.rb
class UserPresenter < DigitalOpera::Presenter::Base
def name
if source.first_name.present? && source.last_name.present?
"#{source.first_name} #{source.last_name}"
else
source.email
end
end
end
Usage
user = UserPresenter.new(User.first)
user.name
# => Chuck Norris
user.first_name = nil
user.name
# => [email protected]
So whats going on? Under the covers, we are implementing the built in Ruby SimpleDelegator class to do our dirty work. By extending SimpleDelegator, our presenter will automatically handle delegating calls to our models instance methods to the model without us having to explicitly set those up. So we can do this:
user = UserPresenter.new(User.first)
user.first_name
# => Chuck
Wrap a collection
If you need to wrap a collection of objects in your Presenter, you can use the wrap
method.
## Will return the collection with each object in the collection having been
## wrapped in the presenter.
##
@users = UserPresenter.wrap(User.all)
Methods
include_in_json
Allows you to define which methods should be included in your presenters output to json using the as_json or to_json methods. This is a class method and is defined on your Presenter class.
class MyPresenter < DigitalOpera::Presenter::Base
include_in_json :name, :avatar_url
def name
[self.first_name, self.last_name].join(" ")
end
def avatar_url
Gravatar.new(self.email).url
end
end
Using the presenter above, we will see the output of name and avatar url be included in the json generated when calling the .as_json
or .to_json
methods.
source
The SimpleDelegator class makes your decorated model available using __getobj__
. We think this is ugly and no good. So we added a simple helper method to the base class called source
that makes everything all better.
user = UserPresenter.new(User.first)
user.class
# => UserPresenter
user.source.class
# => User
Our main purpose for this alias is to use it within the presenter itself to access the model and be more readable than the almighty __getobj__
is.
The View Context
So what good is a "Presenter" that can't present your data using the sweet helpers made available by Rails? None! So we fixed that too. Use the _h
helper from within your presenter to access most of the Rails view helpers (not all have been tested yet so let us know if you find some that don't work).
# app/presenters/user_presenter.rb
class UserPresenter < DigitalOpera::Presenter::Base
def show_me_the_money
# This will return the number formatted as currency using the
# Rails helper number_to_currency
#
_h.number_to_currency 1000
end
end
user = UserPresenter.new(User.first)
user.show_me_the_money
# => "$1,000.00"