pres

Gem Version Build Status

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
    (: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.

References