Presenters
vs
Decorators
Definitions
Decorator
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
src: Wikipedia
class UserDecorator
def full_name
[object.first_name, object.last_name].join(' ')
end
end
Presenter
The Presenter pattern addresses bloated controllers and views containing logic in concert by creating a class representation of the state of the view.
src: Jay Fields' Thoughts
An architecture that uses the Presenter pattern provides view specific data as attributes of an instance of the Presenter. The Presenter's state is an aggregation of model and user entered data.
Exhibit
The primary goal of exhibits is to connect a model object with a context for which it's rendered.
src: Wikipedia
A key differentiator between exhibits and presenters is the language they speak. Exhibits shouldn't know about the language of the view (eg HTML). Exhibits speak the language of the decorated object. Presenters speak the language of the view.
Presentation Model
In this pattern there is no Presenter. Instead the View binds directly to a Presentation Model. The Presentation Model is a Model crafted specifically for the View.
This means this Model can expose properties that one would never put on a domain model as it would be a violation of separation-of-concerns.
src: Stack Overflow
CQ Manager
(examples)
class DishDecorator < Draper::Decorator
include Draper::LazyHelpers
delegate_all
def belongs_to_current_user?
object.user == current_user
end
def order_by_current_user?
object.order.user == current_user
end
def table_buttons
(edit_button.to_s + delete_button.to_s + copy_button.to_s).html_safe
end
def edit_button
if (belongs_to_current_user? && !order.delivered?) ||
(order_by_current_user? && order.ordered?)
link_to 'Edit', edit_order_dish_path(order, object), class: 'margin-right-small'
end
end
def delete_button
if belongs_to_current_user? && order.in_progress?
link_to 'Delete', order_dish_path(order, object), method: :delete
end
end
...
end
tr
th= dish.user.name
th= dish.name
th= dish.price
th= dish.table_buttons
Result
class DishDecorator < Draper::Decorator
include Draper::LazyHelpers
delegate_all
def belongs_to_current_user?
object.user == current_user
end
def order_by_current_user?
object.order.user == current_user
end
end
class DishPresenter
include Rails.application.routes.url_helpers
include ActionView::Helpers
attr_reader :dish, :current_user
def initialize(dish, current_user)
@dish = dish
@current_user = current_user
end
def table_buttons
(edit_button.to_s + delete_button.to_s + copy_button.to_s).html_safe
end
def edit_button
if (dish.belongs_to_current_user? && !dish.order.delivered?) ||
(dish.order_by_current_user? && dish.order.ordered?)
link_to 'Edit', edit_order_dish_path(dish.order, dish), class: 'margin-right-small'
end
end
def delete_button
if dish.belongs_to_current_user? && dish.order.in_progress?
link_to 'Delete', order_dish_path(dish.order, dish), data: {confirm: 'Are you sure?'}, method: :delete
end
end
def copy_button
if (!dish.belongs_to_current_user? && dish.order.in_progress?)
link_to 'Copy', copy_order_dish_path(dish.order, dish), data: {confirm: 'This will overwrite your current dish! Are you sure?'}
end
end
end
- presenter = DishPresenter.new dish, current_user
tr
th= dish.user.name
th= dish.name
th= dish.price
th= presenter.table_buttons
or
class DishDecorator < Draper::Decorator
include Draper::LazyHelpers
delegate_all
...
def presenter
@presenter ||= DishPresenter.new self, current_user
end
end
Gains
- Easier testing
- Smaller classes
- Better decorator reusability
- Less bloated decorator
src: Wikipedia
Changes
Questions?
PresentersDecorators
By Bartek Kruszczyński
PresentersDecorators
- 971