Resubject
Uber simple presenters using Ruby's SimpleDelegator.
Installation
Add this line to your application's Gemfile:
gem 'resubject', '~> 0.3.0'And then execute:
$ bundle
Ruby/Rails versions
This gem is compatible with Ruby 1.9 and 2.0 and Rails 3 and 4.
Documentation
Checkout the documentation in rdoc.info/resubject
Introduction
Resubject uses Ruby's SimpleDelegator class to create its presenters.
SimpleDelegator is a concrete implementation of the Delegator class. Basically, it delegates any method calls to the object passed into the constructor:
require 'delegate'
array = SimpleDelegator.new([1, 2, 3])
array.count # => 3
array.map # => #<Enumerator: ...>It doesn't override the class name, but you still can access the original object.
array.class
# => SimpleDelegator
array.__getobj__.class
# => ArrayThis means you can create a class that inherits from SimpleDelegator and customize its behaviour:
class ForeverZeroArray < SimpleDelegator
def omg!
"OMG!"
end
def count
0
end
endYou can define new methods or override existing ones:
ForeverZeroArray.new([1,2,3]).count
# => 0
ForeverZeroArray.new([1,2,3]).omg!
# => OMG!Usage
Using the Resubject::Presenter class, you can create Presenters from it. Example:
class Box < Struct.new(:name, :items)
end
class BoxPresenter < Resubject::Presenter
def contents
items.join(', ')
end
endThen use the delegator:
box = Box.new('Awkward Package', ['platypus', 'sloth', 'anteater'])
presentable = BoxPresenter.new(box)
presentable.contents
# => platypus, sloth, anteaterIf you have a collection of objects and want to add a presenter to each one, use Resubject.all
boxes = [box1, box2, box3]
BoxPresenter.all boxes
# => [<BoxPresenter>, <BoxPresenter>, <BoxPresenter>]Rails
If you're using rails, Resubject automatically includes helpers in your controllers and views
class BoxesController < ActionController::Base
def index
@boxes = present Boxes.all
end
endThe #present method will automatically identify the presenter class by the object's class name. If you want to customize the class:
def index
@boxes = present Boxes.all, SpecialBoxPresenter
endIt also accepts multiple presenters:
def index
@boxes = present Boxes.all, BoxPresenter, ExtendedBoxPresenter
endOr if you prefer, you can use the #present method directly into your views
<%= present(@box).contents %>Helpers
You can define presentable attributes:
class PostPresenter < Resubject::Presenter
presents :title
presents :comments # or => presents :comments, CommentPresenter
endThen the attributes will return an instance of those presenters:
post.title
# => <TitlePresenter>
post.comments
# => [<CommentPresenter>, <CommentPresenter>, <CommentPresenter>]Or if you wish, you can use the present method inside your class
class PostPresenter < Resubject::Presenter
def comments
present(to_model.comments, SomePresenter)
end
endHelpers on Rails
Resubject can generate some rails helpers for your attributes:
class ProductPresenter < Resubject::Presenter
currency :price, precision: 2
endWill generate:
product.price
# => $10.00Available helpers
Presenter Maps to Class/Module
currency number_to_currency ActionView::Helpers::NumberHelper
percentage number_to_percentage ActionView::Helpers::NumberHelper
time_ago time_ago_in_words ActionView::Helpers::DateHelper
date_format to_s ActiveSupport::TimeWithZone
More helpers will be added. Feel free to contribute with yours! Also, Check out the extensions file.
Helpers without rails
If you'd like to use the generated helpers but you're not using Rails, you can either use ActionView or create your own template handler:
require 'action_view'
post = PostPresenter.new(post, ActionView::Base.new)
# Or
post = PostPresenter.new(post, MyTemplateHandler.new)If you want to use your own template handler and still use Resubject helpers, you may want to define the same ActionView helpers in your handler (or only the ones you will actually use).
Testing
Resubject introduces a new context for RSpec that helps testing your presenters:
# spec/presenters/my_presenter_spec.rb
# require 'action_view' # require this first if you want to test action view helpers
require 'resubject/rspec'
require 'presenters/my_presenter'
describe UserPresenter do
let(:object) { double :user }
it 'has full name' do
object.stub(first: 'User', last: 'Name')
expect(subject.name).to eq 'User Name'
end
endBy placing the file into spec/presenters, Resubject automatically includes the subject and template variables into your spec, so you don't need to define them on every spec.
NOTE: Please note that the presenter is tested on isolation. It's not required but very recommended.
Maintainers
- Felipe Elias Philipp - coderwall.com/felipeelias
- Piotr Jakubowski - coderwall.com/piotrj
Contributing
- 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