pres
What?
A Presenter is a rendering class that wraps a model. Presenters are an alternative to an unorganized mass of helper methods in your Rails application.
The pres gem is a lightweight presenter solution.
How and Why?
Rails' ViewContext contains convenience methods for views, such as link_to,
url_for, truncate, number_to_currency, etc. It's the thing that makes
Rails views nice to work with.
Other presenter libraries mix in all the methods from the Rails ViewContext to
make it easy to call those methods in the Presenter class. This causes method
bloat. pres instead injects the ViewContext as a dependency into the
Presenter class, and uses method_missing to delegate to ViewContext methods.
So pres produces small classes that contain and delegate to an existing object
that handles server-side rendering.
Install
Add it to your Gemfile:
gem "pres"
Include the Pres::Presents module:
class ApplicationController
  include Pres::Presents
end
Add app/presenters to your application's autoload paths in application.rb:
config.autoload_paths += %W( #{ config.root }/app/presenters )
Usage
The quickest way to get started is to use the Pres::Presenter base class.
Create a presenter class in app/presenters:
class DogePresenter < Pres::Presenter
  # explicitly delegate methods to the model
  delegate :name, to: :object
  def know_your_meme_link
    # Rails helpers are available via the view context
    link_to "Know your meme", "http://knowyourmeme.com/memes/doge"
  end
  def name_header
    # object is the Doge
    content_tag(:h1, object.name)  
  end
  def signed_in_status
    # controller methods are accessible via the view context
    if signed_in?
      "Signed in"
    else
      "Signed out"
    end
  end  
end
Wrap your model object in your controller with present:
class DogesController
  def show
  end
  private
  helper_method :doge
  def doge
    @doge ||= present(Doge.find(params[:id]))
  end  
end
Use the presenter object in doges/show.haml.html
= doge.name_header
.status
  You are #{ doge.signed_in_status }
.links
  .meme-link= doge.know_your_meme_link
Collection Example
Create a presenter class in app/presenters:
class DogePresenter < Pres::Presenter
  # same as above
end
Wrap your model object in your controller with present:
class DogesController
  def index
  end
  private
  helper_method :doges
  def doges
    @doges ||= present(Doge.all)
  end  
end
Use the presenter objects in doges/index.haml.html
This renders "doges/_doge.html.haml" for each item, as usual:
= render @doges
Or use each:
- doges.each do |doge|
  = doge.name_header
Present with options
Use keyword arguments (or an options hash) to pass additional options to a Presenter:
class UserPresenter
  def initialize(object, view_context, cool: false)
    super
    @cool = cool
  end  
end
user = User.new
present(user, cool: true)
=> #<UserPresenter object: #<User> ...>
Render a custom Presenter
By default, a presenter class corresponding to the model class name is
constructed. To specify a different class, pass the presenter: key, followed
by any additional arguments:
user = User.new
present(user, presenter: NiceUserPresenter, cool: true)
=> #<NiceUserPresenter object: #<User> ...>
Render a collection
present(User.first(5))
=> [#<UserPresenter object: #<User> ...>]
Creating presenters in views
You should create presenters in your controllers. If you would like to create
a presenter in your view code, make the present method visible to your views:
class ApplicationController
  include Pres::Presents
  helper_method :present
end
More Goodness
Presenters are objects
You can mix in common methods.
module Shared
  def truncated_name(length: 40)
    truncate object.name, length: length
  end
end
class DogePresenter < Presenter
  include Shared
end
You can override methods without resorting to convoluted method names:
class DogePresenter < Pres::Presenter
  include Shared
  def truncated_name(length: 60)
    # whoa this one is different!
    super(length: length)
  end
end
Presenters can create other presenters
class DogePresenter < Pres::Presenter
  def cats
    present(object.cats)
  end  
end
= render doge.cats
Using mixins instead of inheritance
If you don't want to inherit from Pres::Presenter, you can include
Pres::ViewDelegation and implement your own initializer (so the present helper
will work).
class DogePresenter < SimpleDelegator
  include Pres::ViewDelegation
  # you need to write your own initializer with SimpleDelegator
  def initialize(object, view_context, options = {})
    super
    @view_context = view_context
  end
= doge.name
see SimpleDelegator
Updating to version 1.0
Modules and classes have been moved into the Pres namespace with version 1.0.
Change your code references to Pres::Presents and Pres::Presenter.