Simple decorator pattern implementation in Rails

The Decorator Pattern

The formal definition of a decorator is as follows:

The decorator pattern applies when there is a need to dynamically add as well as remove responsibilities to a class, and when subclassing would be impossible due to the large number of subclasses that could result.

Background

Recently I was refactoring a godlike draper decorator, that contained many different methods. On top of that, the decorator was responsible for decorating not only the main object, but also it's relations (lots of has_many and has_one).

I started dividing the methods into logical parts and refactored them into different draper decorators. The problem with this approach, was that I needed several different decorators in one view, because they were constructed using multiple models. Some of them had one or two methods.

Blocks to the rescue

So I decided to roll out my own solution for this. I wanted to use the decorators at will in the view, without prior decoration in the controller. Also I wanted to scope the decoration to a part of a view. So why not use blocks? I quickly came up with this Rails helper method (app/helpers):

module ApplicationHelper  
  def decorate(object, decorator)
    yield decorator.new(object)
  end
end  

This little piece of code gave me a way to reference long method names in a shorthand manner (basicly do variable assignment inside the view, without the actual assignment):

%div
  - decorate(object.with_long_method_name, AwesomeDecorator) do |decorated|
    = decorated.awesome_header

Decorator Pattern Implementation

Now all I needed was a class that would act as a decorator. My first thought was "use draper". But after a couple of seconds I realized I don't need anything so heavy for this job. What I needed was a class, that will call methods on the decorated object, if they don't exist in the decorator class itself. There are a couple of ways to implement this in Ruby.

First way - method_missing

The first way to do this is by using method_missing:

class FooDecorator  
  def initialize(object)
    @object = object
  end

  def method_missing(m, *args, &block)
    @object.send(m, *args, &block)
  end

  def decorated_bar
    puts "This is a decorated #{bar}"
  end

  private
  attr_accessor :object
end

class Foo  
  def bar
    'bar'
  end

  def fiz
    puts "Fiz"
  end
end

decorated = FooDecorator.new(Foo.new)  
decorated.fiz  
decorated.decorated_bar  

The output of this script is:

blog_writing|⇒ ruby method_missing.rb  
Fiz  
This is a decorated bar  

So everything works nice. I can call the new method, defined in the decorator. I can also call methods of the original object.

Second way - #extend

Another way it to extend a class instance on runtime:

module FooDecorator  
  def decorated_bar
    puts "This is a decorated #{bar}"
  end

  private
  attr_accessor :object
end

class Foo  
  def bar
    'bar'
  end

  def fiz
    puts "Fiz"
  end
end

decorated = Foo.new.extend(FooDecorator)  
decorated.decorated_bar  
decorated.fiz  

This code will output the same text, as the method_missing one. The code is more concise, but the drawback here is that we extend a instance with new methods at runtime, which busts the method cache and can be a performance hit.

Third way - SimpleDelegator

Ruby's standard library already has a way to solve this problem. It's called the SimpleDelegator. It's a subclass of Delegator. If you look at the source of the Delegator class, you will see that it uses the method_missing magic to delegate methods to the underlying object. But it does so with some fancy stuff around that - if you want to find out more about it, refer to the ruby source code delegate.rb.

The following code shows how to use SimpleDelegator to implement our decorator:

require "delegate"

class FooDecorator < SimpleDelegator  
  def decorated_bar
    puts "This is a decorated #{bar}"
  end
end

class Foo  
  def bar
    'bar'
  end

  def fiz
    puts "Fiz"
  end
end

decorated = FooDecorator.new(Foo.new)  
decorated.decorated_bar  
decorated.fiz  

This approach is my favourite, as it allows you to change the delegation target at runtime, using the __setobj__ method. This opens up the door for decorators with strategies inside them.

The end

If you have objections of passing class names as attributes inside the view, you can define helper methods on your controllers, that will return the proper decorator. But I think this way is pretty neat, because you can do nested decoration:

%div
  - decorate(object.with_long_method_name, AwesomeDecorator) do |decorated|
    = decorated.awesome_method
    - decorate(decorated, MoreAwesomeDecorator) do |super_decorated|
      = super_decorated.even_more_awesome_method

I don't see any practical application for this, but hey, it's nice to have options!

Bonus

If you want to use Rails helper methods (like image_tag, link_to, etc.) inside your custom decorator, you can define the following method:

def h  
  ActionController::Base.helpers
end  

And use it like `h.image_asset, just like in draper!