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

 
29
Kudos
 
29
Kudos

Now read this

Search Filters and Object Oriented Design

Search filters are a very common feature in any application. I’ve used two gems for several projects I worked on: has_scope and searchlight. Both gems work out of the box, but they are not as flexible as I would like them to be, and that... Continue →