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"