Homemade Decorators
Ever since I started writing Rails applications, people always told me to use Decorators instead of Helpers.
So what’s a Decorator? #
Decorator is a design pattern. Its intent to attach additional responsabilities to an object dynamically. One common use of Decorators on Rails is to group view logic of our Models. This way we’ll keep the business logic in our Models separated from our view logic, otherwise will be breaking the Single Reponsability Principle. You can think of it, as a view logic’s wrapper for our Model.
How does it work? #
First thing we are going to do, is creating our Decorator class which should inherit from SimpleDelegator class which comes built-in with ruby:
#app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
end
Inheriting from SimpleDelegator will make that all methods called from our UserDecorator instance will be delegated to the Object passed as argument when we created the decorator instance. Just like this:
user = User.new(first_name: 'John', last_name: 'Smith', age: '30')
user_decorator = UserDecorator.new(user)
user.first_name # => 'John'
user_decorator.first_name # => 'John'
Now that we created our UserDecorator class, lets add a new method for displaying our user’s full name.
#app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
def full_name
"#{first_name} #{last_name}"
end
end
So now, in the users controllers we’ll decorate the user model instance we found.
#app/decorators/user_decorator.rb
class UsersController < ApplicationController
def show
user = User.find(params[:id])
@user = UserDecorator.new(user)
end
end
And finally in our view file, we’ll display our user’s full_name:
#app/views/users/show.html.erb
<h1>User Info</h1>
User's Fullname: <%= @user.full_name %>
User's Age: <%= @user.age %>
That’s all what decorators can do? #
Well, not really. Decorators are also useful when you want to change the behavior of certain method of an object. Lets say for example that we have two kind of calendar events, events and submitted events. The only difference between them is that the submitted events stores the user who submitted the event, so we are going to store events and submitted events on the same place.
First, lets define our event model:
#app/models/event.rb
class Event < ActiveRecord::Base
belongs_to :user # Returns the user who submitted the event if it's a submitted kind otherwise returns nil
end
Now lets create our SubmittedEvent class, only this time we’ll loose the Decorator from the name of the class and we’ll store this class on the models’ folder:
#app/models/submitted_event.rb
class SubmittedEvent < SimpleDelegator
attr_reader :user
def initialize(user)
@user = user
event = Event.new(title: 'submitted event')
super(event) # This will tell SimpleDelegator to delegate all methods to event
end
def save
self.user = user # Associates the submitted event with a user
super # Calls the parent's object (that would be Event model) save method
end
end
So now that we have our SubmittedEvent’s class, lets create a submitted event:
# User who sent the event
user = User.create(first_name: 'John', last_name: 'Smith', age: '30')
# Creates submitted event instance and passes the user who sent the event as an argument
submitted_event = SubmittedEvent.new(user)
# Tells the submitted event to associate this event with the user who sent the event
# and then calls the Event model save method
submitted_event.save
Conclusion #
Decorators are very useful sometimes to extract logic from your models or controllers. Remember that you don’t want to end up having god classes, fat models or fat controllers.
Resources #
- There is a pretty good gem to work with Decorators named Draper, it also has some cool features and works great with Rails. I suggest you to read the documentation and give it a try.
- SimpleDelegator Documentation
- Decorator Pattern