Decorators And Presenters In Rails
Stuffing ALL THE THINGS into view templates is a pain for front end developers, is error prone, violates the DRY principle and is difficult to test.
What would be the solution ?
* Helpers
* Decorators
* Presenters
..many more design patterns ..
Helper is very familiar among Rails Community and needs no introduction.But a question that comes to mind is Why do we need decorators when we have HELPERS ?
Aside from not being very object oriented, helpers have a host of problems related to their global availability. Helpers should be avoided if they are specifically tied to a single model.Meaning to say use helpers only when there are concepts which affect the presentation logic across the application.
Example:
app/helpers/application_helper.rb
module ApplicationHelper
def back_button(options = {})
link_to t('forms.back'), :back, class: 'btn'
end
end
app/helpers/user_helper.rb
module BlogHelper
def published_at(blog)
if
blog && blog.published_at.present?
"Posted: #{blog.published_at.strftime('%m, %d, %y')}"
end
end
end
The back_button in ApplicationHelper is a presentation logic which is required across the app and is apt to be put in the helper. However we wouldn't need the formatted Blog#publised_at in all our views.So this calls for a DECORATOR !
Decorator Pattern is a design pattern that is used to extend the functionality of a specific object by wrapping it, without affecting the behaviour of other objects from the same class.
Decorators stand between the model and the view.The decorator object will know of the model and have access to the view’s helper method.Decorators works a great solution for model based presentation code.
Draper is a popular rails decorator gem.For more information DIG IN.
Example:
app/decorators/blog_decorator.rb
class BlogDecorator < Draper:Decorator
def published_at
"Published #{object.published_at.strftime('%A, %B %e')}"
end
end
app/controller/blogs_controller.rb
def show@blog = Blog.find(params[:id]).decorate end
app/views/blogs/show.html.erb
<%= @blog.published_at %>
As you can see, the decorator pattern is a very good example of the Open-Close-Principle (OCP) which states that an class is closed to modification but open to extension. One thing to note here is that the unlike helpers the formatted published_at will be available only to the blog objects.
Now let's move on to Presenters. Presenter is a form of decorator. Then what would be the difference between a decorator and a presenter.
Presenter design pattern is a way of simplifying controller logic by extracting it out into its own class which has one or more methods.
Presenters make it easier to test controller logic by usually transforming each assigned instance variable in a controller action into its own method within the presenter class. Anytime you see a controller action which has many instances variables, it needs to be refactored to use a presenter.
The main difference between the two is that a decorator pattern is a composing operation whereas the presenter pattern is a decomposing operation.
Example:
app/controllers/blogs_controller.rb
class BlogsController < ApplicationController
def show
blog = Blog.find(params[:id])
@blog = blog.decorate
@author = blog.author.decorate
@contributions = @author.contributions.decorate
@followers = @blog.followers.decorate
end
end
So now can see you controllers growing making it difficult to test and maintain.Now this calls for a PRESENTER !
Presenters are simply Plain Old Ruby Objects(PORO) with the help of which you can clean up this messy code.
Example:
class BlogPresenter
def initialize(blog_id)
@blog = Blog.find(blog_id)
end
def decorated_blog
@decorated_blog ||= @blog.decorate
end
def author
@author ||= @blog.author.decorate
end
def contributions
@contributions ||= @author.contributions.decorate
end
def followers
@followers ||= @author.followers.decorate
end
end
app/controller/blogs_controller.rb
class BlogsController < ApplicationController
def show
@presenter = BlogPresenter.new(params[:id])
end
end
and can be accessed using <%= @presenter.author %>
So in this way we could decompose the controller actions and maintain a clean code base.
Finally the PROS in using Presenters & Decorators
- More maintainable and clean codebase
- DRY
- Clarity
- Easy to Write Tests