Tuesday, November 6, 2007

A bit of metaprogramming

I added some functionality to the technique shown in this Jay Fields post so that you can retrieve your original method(s). You can use it for aspect programming but remember that if you want a real framework for doing so then just use AspectR or some similar library.

First you add your behaviour to an existent method:

class TestClass
def testMethod(param1, param2)
"Original output with parameters #{param1} and #{param2}"
end
end

class TestClass
after_method :testMethod do |returnValue, param1, param2|
"The original output is: " + returnValue
end
end

If now you call testMethod('a', 'b') you'll get this string:
The original output is: Original output with parameters a and b, so you successfully decorated the initial method.

Now if you want to get the original behaviour and remove your method decoration, just do:

TestClass.undo_after_method(:testMethod);

And now after sending testMethod('a', 'b') you'll get:
Original output with parameters a and b.

Here is the code:

class Class
def after_method(method_symbol)
previous_method = instance_method(method_symbol)

define_method(method_symbol) do |*args|
returnValue = previous_method.bind(self).call(*args)
yield returnValue, *args
end

singleton_class.push_previous_method(previous_method)
end

def undo_after_method(method_symbol)
method_key = instance_method(method_symbol).to_s.to_sym
previous_method = singleton_class.pop_previous_method(method_key)
if previous_method
define_method(method_symbol) do |*method_args|
previous_method.bind(self).call(*method_args)
end
end
end

def singleton_class
class<<Class;self;end;
end

class<<Class
#A hash that holds all the stacks.
#Each stack holds the current set of behaviours that decorate the initial method
@previousMethodsStacks = {}

def pop_previous_method(method_key)
previous_methods_stack = previous_methods_stacks[method_key]

if(previous_methods_stack and previous_methods_stack.size > 0)
previous_method = previous_methods_stack.pop

if(previous_methods_stacks[method_key].size == 0)
previous_methods_stacks.delete(method_key)
end
end
previous_method
end

def push_previous_method(previous_method)
previous_methods_stack = previous_methods_stacks.fetch(previous_method.to_s.to_sym) do
previous_methods_stacks[previous_method.to_s.to_sym] = []
end
previous_methods_stack.push(previous_method)
end

def previous_methods_stacks
@previousMethodsStacks
end
end
end

The RSpec tests are here.

0 comments: