The Decorator Pattern

Note: I've updated my thoughts on Draper in this post


What Is The Decorator Pattern?

In object-oriented programming, the decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter 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.[1] The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.

- Taken from Wikipedia

A decorator allows us to modify an object without affecting the other objects with the same class. This is particularly useful in cleaning up view logic and removing helpers.


Examples


Perhaps we've created a turn by turn zombie game. We need to display a dynamic message based on the health status of the zombie to the user so we create a helper method

# app/helpers/zombie_helper.rb

def health_status(zombie)  
  if zombie.is_decapitated
    "Killed at #{zombie.decapitated_at.strftime('%A, %B %e')}"
  esle
    "Undead"
  end
Issues

This helper will be included in all controllers and views by default.

It's possible to configure the app to only serve the helpers correlating to the current namespace but we lose the ability to use methods for other classes.

The methods is ambiguous. Consider we create a new model, class Human < ActiveRecord::Base. This will be our avatar in the game. So we need a way to show the health_status for our Humans, but we already have a helper method named health_status. We could refactor the method to return the correct message based on the methods argument class def health_status(object) this would make for a long, messy method. Or we could rename create a new method def human_health_status(human) and rename the zombie helper def zombie_health_status(zombie), this is better but still doesn't fix the fact that all controllers/views can access all of these methods, the larger the application the bigger this issue becomes.

Using Draper to Decorate Our Views

There's a better way to do this, let's use Draper to refactor the zombies helper.

# app/decorators/zombie_decorator.rb
class ZombieDecorator < Draper::Decorator  
  def health_status
    if object.is_decapitated?
      "Killed at #{decapitated_at}"
    esle
      "Undead"
    end
  end


  def decapitated_at
    object.decapitated_at.strftime("%A, %B %e")
  end
end  

Now we can control the initialization of these extra methods, simply call .decorate on the record

# app/controllers/zombies_controller.rb
def show  
  @zombie = Zombie.find(params[:id]).decorate
end  

Now in the views we call the method on the record object itself, giving more context to the method

<%= @zombie.health_status %>  

Ever seen this in an app?

# app/views/articles/show.html.erb
<% if user_signed_in? %>  
  <%= link_to 'Read more', article_path(@article) %>
<% else %>  
  <%= link_to 'Read more', sign_in_path %>
<% end %>  

We can use the same pattern to refactor this logic out of the view.

# app/decorators/article_decorator.rb
def read_more_link  
  if h.user_signed_in?
    h.link_to 'Read more', article_path(object)
  else
    h.link_to 'Read more', sign_in_path
  end
end  
# app/views/articles/show.html.erb
<%= @article.read_more_link %>  

Further Reading


Evaluating Alternative Decorator Implementations In Ruby - by Dan Croak at Thoughtbot

Decorators Compared To Strategies, Composites, and Presenters - by Dan Croak at Thoughtbot

Design Patterns - by the gang of four

Draper Railscast - by Ryan Bates at Railscasts

Draper Readme

Joe Woodward

Read more posts by this author.