RailsPresenter will help you to clean up your views and avoid helpers hell.
How does it work?
Basically there are two main components, the
presenter object and the
#present helper method.
The presenter object
You can think of a presenter as a mix between a domain model object and a view template, every method call not defined in the current class will be forwarded to the original domain model object, besides you can access all the view template functionality through the
#h method. Also the
#target (also aliased as
#object) method will get you the unmodified original domain model object.
class ProductPresenter < RailsPresenter::Base def image h.link_to(h.image_tag(super), h.product_path(target)) end end
The #present helper method
The helper method
#present it's used to instantiate new presenter objects, it takes any object, an array of objects or an ActiveRecord::Relation and returns the corresponding presenter instances.
present(Customer.new).map(&:class) # => CustomerPresenter present([Customer.new, Product.new]).map(&:class) # => [CustomerPresenter, ProductPresenter] present(Product.limit(2).order(:name))).map(&:class) # => [ProductPresenter, ProductPresenter]
This method determines the name of the presenter class from the target object, for example a Project object would instantiate a ProjectPresenter object. If the assumed presenter class doesn't exist it will return the unmodified target object.
You can pass an optional block too, in fact this the intended usage of the helper in your views:
present(@purchase_order) do |purchase_order_presenter| purchase_order_presenter.date purchase_order_presenter.number end
Define the associated objects that you want to get automatically presented.
class Post has_many :comments belongs_to :user end class PostPresenter < RailsPresenter::Base present :comments, :user end post_presenter = present(Post.last) post_presenter.comments.first.class # => CommentPresenter post_presenter.user.class # => UserPresenter
super at will
You can very easily add functionality on top of what RailsPresenter already provides, you just have to redefine your method and call
super, class inheritance, module mixin, everything works as expected, as RailsPresenter uses a set of well identified (not anonymous) modules to extend functionality.
class SupplierPresenter < CompanyPresenter; end class CompanyPresenter < RailsPresenter::Base; end SupplierPresenter.ancestors # => # [SupplierPresenter, # SupplierPresenter::NumberToCurrency, # SupplierPresenter::SupplierPresenterAssociations, # SupplierPresenter::BlankAttributes, # CompanyPresenter, # CompanyPresenter::BlankAttributes, # RailsPresenter::Base, # etc, etc...]
Are you crazy? We already have Draper!!
Well yes, Draper it's an amazing library and it inspired RailsPresenter in many ways (and was developed by people way more smarter than me), so I will try to illustrate what motivated me to reinvent the wheel.
At a basic level both provide the same functionality, but for my personal needs I find Draper too complex and with too many options, I prefer a simpler interface and good conventions, besides RailsPresenter implements a set of presentation related functionality on top of basic delegation as you can see on the aforementioned features. Additionally RailsPresenter it's meant to be used only inside the views, through the
#present helper, so it doesn't provides any controller related functionality.
On the other side Draper has a much more fine-grained control over the methods delegated to the target object, RailsPresenter just will delegate every method called not defined in the presenter.
Decorating a single object
# Draper way Article.first.decorate ArticleDecorator.new(Article.first) ArticleDecorator.decorate(Article.first) # RailsPresenter way present(Article.first)
Decorating a collection
# Draper way ArticleDecorator.decorate_collection(Article.all) Article.popular.decorate # this only works for an ActiveRecord relation [Article.first, Comment.last, User.find(3)].decorate # you can't do this # RailsPresenter way present(Article.all) present(Article.popular) present([Article.first, Comment.last, User.find(3)]) # you can decorate arbitrary arrays
# Draper way class Article < ActiveRecord::Base # I think the following scope it's completely view related and doesn't belongs here def self.comments_with_author_included_and_ordered_by_created_at comments.includes(:author).order('comments.created_at') end end class ArticleDecorator < Draper::Decorator decorates_association :comments, scope: :comments_with_author_included_and_ordered_by_created_at end # RailsPresenter way class ArticlePresenter < RailsPresenter::Base present :comments do includes(:author).order('comments.created_at') end end
- Railscast: http://railscasts.com/episodes/287-presenters-from-scratch
- Book: http://pragprog.com/book/warv/the-rails-view
- Gem: https://github.com/drapergem/draper
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install rails_presenter
- Fork it
- Create your feature branch (
git checkout -b my-new-feature)
- Commit your changes (
git commit -am 'Add some feature')
- Push to the branch (
git push origin my-new-feature)
- Create new Pull Request
MIT License. Copyright 2013 Diego Mónaco