In my previous post From Decorator Pattern to Before Filter 1 we got to the point where we could write this:
coffee = Coffee.new
coffee.mixin Callback
coffee.before_filter :spoon_required, :cost
coffee.cost
Now the thing missing is that we’re only filtering one method (‘cost’ in this case) wheras the before_filter in rails filters all methods by default. So how do we do this?
Quit easily, we just have to list all the instance methods inside the class Coffee (ruby provides this possiblity with “instance_methods(false)”)…. and… iterate!:
require 'rubygems'
require 'mixology'
module Callback
def filter_with filter, filtered_method
Callback.method_factory(filter, filtered_method)
end
def before_filter filter
methods = self.class.instance_methods(false)
methods.delete(filter.to_s)
methods.each do |filtered_method|
filter_with filter, filtered_method
end
end
def self.method_factory(filter, filtered_method)
define_method(filtered_method) do
send filter
super
end
end
end
class Coffee
def spoon_required
puts "get a spoon to steer sugar || creme"
end
def cost
puts "3"
end
def smell
puts "good"
end
def color
puts "black"
end
end
coffee = Coffee.new
coffee.mixin Callback
coffee.before_filter :spoon_required
# Every call beneath is prepended by :
# - "get a spoon to steer sugar || creme"
coffee.cost
coffee.smell
coffee.color
After that adding options like :except=>”smell” is easy. Dealing with inheritance is probably less obvious. The one main differance we have with the rails version of before_filter is the fact that it is set inside the classes body. Not at object creation, and for a good reason since controllers instances are created under the hood.
Of course the easy path is adding: extend(Creme) and before_filter(:spoon_required) in the Coffee’s initialize method, but my question was: “is it possible to make it look like this” :
class Coffee
include Creme
before_filter :spoon_required
def spoon_required
puts "get spoon"
end
def cost
puts "3"
end
end
The answer is yes although it’s a bit convoluted (it looks like a rail plugin with module InstanceMethod and so on) AND we still have to overide initialize somewhere which makes it dangerous.
Now one word of explanation: the hole elegance of the Decorator Pattern in ruby relies on the fact we’re calling super instead of aliasing twice the method we’re decorating (or unbinding and binding). Calling super() is possible because when we extend an object with a module (like in coffee.extend Creme) we’re actually including the module in the ghostclass (or metaclass or singleton class) which sits right underneath the ‘real’ class in the inheritance chain.
(TODO add figure)
So wouldn’t be nice if we could mixin a module ‘underneath’ a class by saying so in the body of the class so we could easily do all that aspect oriented stuff without having to worry about it at instanciation time ?
The great Library Facets, gives it a try with prepend which can be used this way :
require 'rubygems'
require 'facets'
module Creme
def cost
puts "before_filter"
super
end
end
class Coffee
prepend Creme
def cost
puts "3"
end
end
coffee = Coffee.new
coffee.cost
#But guess what, it's by overriding new:
# facet library
def prepend( aspect )
_new = method(:new)
_allocate = method(:allocate)
(class << self; self; end).class_eval do
define_method(:new) do |*args|
o = _new.call(*args)
o.extend aspect
o
end
define_method(:allocate) do |*args|
o = _allocate.call(*args)
o.extend aspect
o
end
end
end
Comments welcome !