Decorator Pattern in Ruby on Rails
What is the Decorator Pattern?
It allows us to add functionality to an object of a class without affecting other instances of that class. The term "decorator" originates from the pattern of adding additional behavior onto a single object, among all the instances of the class you have a "decorated" instance.
How do we use that in Ruby on Rails?
A common requirement in projects is to alter data in a model for the view layer. For example, say you have a User
model with attributes first_name
and last_name
and you want to display the full name of the user in the view.
In order to keep presentation logic out of the views and models*, which should always be the goal,* you can add it into a decorator class instead. This simplifies our views which should exclude logic as much as possible, and models which should not know about presentation. Then, we can decorate our instances as we need.
How do you implement the Decorator Pattern?
View Logic:
Let's say we have a User
class and an Article
class. In two separate views, the User
show page and Article
index, we display the full name of the user by combining the user.first_name
and user.last_name
On the user's profile page:
# apps/views/users/show.html.haml
%h1.name-display
= @user.first_name + " " + @user.last_name
In the index page of articles:
# apps/views/articles/index.html.haml
%h1= @article.title
.author Written By #{@article.user.first_name + " " + @article.user.last_name}
Code Smell:
Right away, we should detect a code smell! Firstly, we're repeating code in multiple places. Secondly, we're manipulating data directly in the view which we should avoid. Remember, the view should just be there and display things.
The Solution:
We can add a decorator pattern to improve this!
- Define a subdirectory,
app/decorators
- Define a file,
app/decorators/user_decorator.rb
In this file we can define a name_display
method which will contain the string manipulation logic to show the full name of the user. The class will inherit from SimpleDelegator
, a native Ruby class that allows you to delegate methods calls to the object passed in during construction.
As stated in the Ruby docs:
"this class provides the means to delegate all supported method calls to the object passed into the constructor" - ruby-doc
# app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
def name_display
#{first_name last_name}
end
end
Implementing the Decorator into our Views
Now we can use our decorators to simplify our views. First in the user's profile page:
# apps/views/users/show.html.haml
%h1.name-display
= UserDecorator.new(@user).name_display
Then in the article index page:
# apps/views/articles/index.html.haml
%h1= @article.title
.author Written By #{UserDecorator.new(@article.user).name_display}
Alternative: Using the Draper Gem
If you don't want to create your own decorator class from scratch, you can also use the Draper Gem.
With Draper, you could write a Decorator Class like:
# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
delegate_all
def name_display
#{object.first_name object.last_name}
end
end
Then, you would call decorate
in your controller like so:
# app/controllers/users_controller.rb
def show
@user = User.find(params[:id]).decorate
end
And in your views:
# app/views/users/show.html.haml
%h1.name-display
= @user.name_display
Additional Resources
- Decorator Pattern in Java: TutorialsPoint
- Decorator in Ruby RefactoringGuru
Previous post: How to create a multi-step form in Ruby on Rails
Next post: Helpers vs. Concerns in Rails