Variations on the Decorator Design Pattern in Ruby have already been discussed in several places. For my special purpose, none of these approaches works.
The common problem with a decorator based on delegation is the lost sense of self. Method calls of the decorated object to itself are not routed through the decorator, therefore remain woefully undecorated.
The Ruby idiom of aliasing and redefining methods, in effect adding around advice, is possible, when the decoration applies to all uses of the “core” class. It is not suitable when you need decorated as well as undecorated variants.
There’s an almost classical: Just derive a subclass from the “core” class and add the decoration in there. Doing this explicitly is tedious, ugly, and multiplies classes semper necessitatem. Luckily, for us and poor, overquoted Father Ockham, Ruby is a highly dynamic language where things are possible that users of other languages can only dream of (longingly or in nightmares, that’s quite a different issue).
So, without further ado, here’s a simple demonstration of the technique.
class Core
def m1
puts "Core#m1"
end
def m2
puts "Core#m2"
m3
end
def m3
puts "Core#m3"
end
def m4
puts "Core#m4"
end
end
class Decorator
class << self
def new(*args)
decorate(Core).new(*args)
end
private
def decorate(klass)
decorated_class = Class.new(klass)
decorated_class.send(:include, Decoration)
decorated_class
end
end
module Decoration
def m1
puts "Decorator#m1"
super
end
def m2
puts "Decorator#m2"
super
end
def m3
puts "Decorator#m3"
super
end
end
end
With that under under our collective belt, here’s the case that motivated my attempt at self-conscious delegation. In a Rails application, I have several layers of FormBuilders. At the base, derived from ActionView::Helpers::FormBuilder is a form builder that provides mechanisms. For example for adding labels to input elements and for automatically displaying appropriate widgets for various association types. On top of that, I have two form builders that add layout specifics; one for CSS-based layouts, one for table-based layouts. I still need a further layer for policy: only show users what they are allowed to see and touch. This last policy layer is, of course, independent of layout (and vice versa!), therefore, short of full-fledged AOP, decoration was the way to go.
class DecoFormBuilder
class << self
def new(object_name, object, template, options, proc)
delegate_class = options.delete(:delegate_builder)
raise ArgumentError, 'No :delegate_builder given to DecoFormBuilder' unless delegate_class
decorate(delegate_class).new(object_name, object, template, options, proc)
end
private
def decorate(klass)
decorated = Class.new(klass)
decorated.send(:include, Decoration)
decorated
end
end
module Decoration
def initialize(object_name, object, template, option, proc)
super
# Do whatever initialization you need here
end
# Put the decorating methods here and don't forget to
# call super.
end
end