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 a 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 its 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!