Representers for Rails
A possible representer solution, i.e. no view logic in model code.
Ask/Write [email protected] if you have questions/feedback, thanks! :)
Fork if you have improvements. Tell me where to merge them from, thanks!
Problem: Display Methods are not well placed either in
* models: Violation of the MVC principle.
* helpers: No Polymorphism.
Solution:
A thin proxy layer over a model, with access to the controller, used by the view or controller.
IMPORTANT NOTE:
As of yet it is needed to copy the representer/views/representers/collection
directory to the corresponding location in app/views/representers/collection.
This is only needed if you wish to use the collection rpresenter.
Note: Rewrite the collection templates as needed, they are rather basic.
* Getting a representer in a view or a controller.
Call representer_for:
Note: By convention, uses Representers::Model::Class::Name, thus prefixing Representers:: to
the model class name.
presenter_instance = representer_for model_instance
* Getting a collection representer in a view.
The collection representer renders each of the given items with its representer.
Call collection_presenter_for:
collection_presenter_instance = collection_presenter_for enumerable_containing_model_instances
Rendering a list.
collection_presenter_instance.list
Rendering a collection.
collection_presenter_instance.collection
Rendering a table.
collection_presenter_instance.table
Rendering a pagination.
Note: Only works if the passed parameter for collection_presenter_for is a PaginationEnumeration.
collection_presenter_instance.pagination
* Writing filtered delegate methods on the representer.
Will create two delegate methods first_name and last_name that delegate to the model.
model_reader :first_name, :last_name
Will create a description delegate method that filters the model value through h.
model_reader :description, :filter_through => :h
Will create a description delegate method that filters the model value through first textilize, then h.
model_reader :description, :filter_through => [:textilize, :h]
Will create both a first_name and last_name delegate method
that filters the model value through first textilize, then h.
model_reader :first_name, :last_name, :filter_through => [:textilize, :h]
Note: Filter methods can be any method on the representer with arity 1.
* Rendering representer templates
Use render_as(template_name, format = nil).
Gets a Representers::Model::Class instance
presenter = representer_for Model::Class.new
Gets a Representers::<model_instance.class.name> instance
presenter = representer_for model_instance
Renders the 'example' partial in representers/model/class.
Note: Renders a format depending on the request. ../index.text will render example.text.erb.
presenter.render_as :example
Renders the 'example.text.erb' partial in representers/model/class.
presenter.render_as :example, :text
* Rails Helpers in Representers
Use helper as in the controller.
helper ActionView::Helpers::UrlHelper
helper ApplicationHelper
Note: It is helpful to create a superclass to all representers in the project
with generally used helpers.
We use Representers::Project a lot, for example. See example below.
* Controller Delegate Methods
Use controller_method(*args).
Delegates current_user and logger on the representer to the controller.
controller_method :current_user, :logger
* Big Example
The following classes all have specs of course ;) But are not shown since they don't help the example.
Representers superclass for this project.
We include all of Rails' helpers for the representers in this project.
Also, we include the ApplicationHelper.
We delegate logger and current_user calls in the representers to
the active controller.
class Representers::Project < Representers::Base
All of Rails' standard helpers.
helper ActionView::Helpers::ActiveRecordHelper
helper ActionView::Helpers::TagHelper
helper ActionView::Helpers::FormTagHelper
helper ActionView::Helpers::FormOptionsHelper
helper ActionView::Helpers::FormHelper
helper ActionView::Helpers::UrlHelper
helper ActionView::Helpers::AssetTagHelper
helper ActionView::Helpers::PrototypeHelper
helper ActionView::Helpers::TextHelper
helper ApplicationHelper
controller_method :logger, :current_user
end
All items have a description that needs to be filtered by textilize.
class Representers::Item < Representers::Project
model_reader :description, :filter_through => :textilize
Use price in the view as follows:
= representer.price - will display e.g. 16.57 CHF, since it is filtered first through localize_currency
model_reader :price, :filter_through => :localize_currency
Converts a database price tag to the users chosen value, with the users preferred currency appended.
If the user is Swiss, localize_currency will convert 10 Euros to "16.57 CHF"
def localize_currency(price_in_euros)
converted_price = current_user.convert_price(price_in_euros)
"#{converted_price} #{current_user.currency.to_s}"
end
end
This class also has partial templates in the directory
app/views/representers/book
that are called
_cart_item.html.haml
_cart_item.text.erb
Call representer_for on a book in the view or controller to get this representer.
class Representers::Book < Representers::Item
model_reader :author, :title, :pages
model_reader :excerpt, :filter_through => :textilize
def header
content_tag(:h1, "#{author} – #{title}")
end
def full_description
content_tag(:p, "#{excerpt} #{description}", :class => 'description full')
end
end
This class also has partial templates in the directory
app/views/representers/toy
that are called
_cart_item.html.haml
_cart_item.text.erb
Call representer_for on a toy in the view or controller to get this representer.
class Representers::Toy < Representers::Item
model_reader :starting_age, :small_dangerous_parts
def obligatory_parental_warning
"Warning, this toy can only be used by kids ages #{starting_age} and up. Your department of health. Thank you."
end
end