A Presenter is a rendering class. The pres
gem is a lightweight presenter
solution with no runtime gem dependencies.
Pres
provides the following:
Pres::Presenter
is a presenter base class.present
is a convenience method to create presenters.Pres::ViewDelegation
is a delegation module, included in thePresenter
base class.
Presenters are an alternative to an unorganized pile of helper methods in your rails application.
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. pres
instead injects
the ViewContext
as a dependency into the Presenter class, and uses method_missing
to delegate to ViewContext
methods. pres
produces small classes that contain and
delegate to an existing object that handles server-side rendering.
Add it to your Gemfile:
gem "pres"
Include the Pres::Presents
module:
class ApplicationHelper
include Pres::Presents
end
This will make the present
method available in your views.
There are two main approaches:
(1) Follow the traditional rails way with view templates, but move your helper methods into a presenter class. You'll probably want to start here if you have an existing rails app.
(2) Create self-contained rendering components (see "Components" below).
You can use both techniques.
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 used to initialize the presenter
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
Standard rails controller method:
class DogesController
def show
@doge = Doge.find(params[:id])
end
end
Wrap your model object in a presenter in your view with present
:
doges/show.haml.html
- present(@doge) do |doge|
= doge.name_header
.status
You are #{doge.signed_in_status}
.links
.meme-link= doge.know_your_meme_link
Standard rails controller method:
class DogesController
def index
@doges = present(Doge.all)
end
end
Build an array of presenters in your view with present
:
doges/index.haml.html
This renders "doges/_doge.html.haml" for each item, following rails' usual conventions:
= render present(@doges)
Or use each:
- present(@doges).each do |doge|
= doge.name_header
You can use pres
to build components that directly render HTML:
Note: #sanitize
is a method on the view_context
.
class NameHeader < Pres::Presenter
def render
return unless object&.name
<<~HTML.html_safe
<h1>#{sanitize(object.name.titleize)}</h1>
HTML
end
end
user = User.new(name: "joe cool <")
NameHeader.new(user, view_context).render
=> "<h1>Joe Cool <</h1>"
present(user, presenter: NameHeader).render
=> "<h1>Joe Cool <</h1>"
You may notice that you could do without pres
altogether when you don't need
the view_context
helper methods:
class PlusTwo
def initialize(object)
@object = object
end
def render
return unless @object
<<~HTML.html_safe
<p>#{@object + 2}</p>
HTML
end
end
PlusTwo.new(2).render
=> "<p>4</p>"
present(2, presenter: PlusTwo).render
=> "<p>4</p>"
If render
is confusing, name that method #to_html
or something else.
Pass additional options to a Presenter as a hash. The presenter class exposes the
options
hash as a method:
user = User.new
# These two lines are the same:
# 1. explicit
presenter = UserPresenter.new(user, view_context, something: 123)
# 2. using #present
presenter = present(user, something: 123)
=> #<UserPresenter object: #<User> ...>
presenter.options[:something]
=> 123
By default, a presenter class corresponding to the model class name is
constructed in present
. For example, if you present a User
, a UserPresenter
class is constructed. An error is raised if the presenter class does not exist.
To specify a different class, use the presenter:
key.
user = User.new
present(user, presenter: UserEditPresenter, cool: true)
=> #<UserEditPresenter object: #<User> ...>
You may also define a custom presenter class on any class you want to present:
class User
def presenter_class
MyPresenter
end
end
present(User.new)
# => #<MyPresenter object: #<User> ...>
Presenters can wrap child objects in presenters of their own.
class DogePresenter < Pres::Presenter
def cats
present(object.cats)
end
end
= render doge.cats
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).
This technique is useful if you would like to delegate all methods in a model
by default, instead of whitelisting methods on the wrapped model explicitly.
Delegating everything to the model by default is how the draper
gem works, for example.
class DogePresenter < DelegateClass(Doge)
include Pres::ViewDelegation
def initialize(object, view_context, options = {})
super(object)
@view_context = view_context
end
= doge.name
see DelegateClass
Modules and classes have been moved into the Pres
namespace with version 1.0.
Change your code references to Pres::Presents
and Pres::Presenter
.