If you ever traverse through some Rails projects you will most likely come across the use of Module#alias_method_chain. It basically places a function as a proxy in between the original call and the actual method call. It makes it easy to define some Aspect oriented programming which makes it easy to add logging and performance measuring to a method call.
Last autumn I played around with some Ruby metaprogramming regarding methods. I decided to write a simple DSL for doing some AOP in Ruby. Not because it is going to be overly useful for everyone but more for the fun of it.
What better than a DSL to explain the functionality.
There are a few gems already providing this functionality and much more for Ruby http://aquarium.rubyforge.org/ and a few more https://www.google.com/search?btnG=1&pws=0&q=gem+aspect+oriented.
My core thoughts on Tivoli are: It should be dead simple, not pollute any object and provide a way of doing AOP in an adhoc manner. (Meaning existing code should not be concerned that we are logging or measuring performance).
Let me guide you through the thoughts that led me to Tivoli.
Disclaimer: We are about to tread into meta programming, please choose cautiously what you would use in your production code.
A word on syntax:
In Ruby, things are quite different for methods but similar for procs/lambdas. Ruby methods are in fact called when referred without parentheses and arguments which makes for nice short and clean syntax and compensates by using blocks when functionality needs to be passed around.
I think the design choices in Ruby are quite solid and bring the nice flexibility that we come to expect when expressing ourselves in Ruby. I encourage reuse of blocks in this manner as it is often a feature people tend to overlook as a way of DRYing up their code.
Even the block syntax feels clumsy at times for simple cases. Which got us the Symbol#to_proc and encourages some to work outside of the simple cases.
The short syntax that you see above unfortunately only captures the cases when the methods are in fact defined on the object.
So given a class method in this example JSON#parse we could create a proc that proxies the call and reuse it.
Reading the docs on the Method Class confirms that a Ruby method is defined in the context of a object, meaning that there is a #reciever and an #owner method on a Method.
This got me thinking… There’s got to be a way of reusing a method’s definition for multiple class and at the same time satisfying my needy DRY addiction while removing the overhead of having a separate block for proxying every call.
We define a method #twice on Numeric
Now lets say we want to reuse this method. How do we get around so that a method is bound to a reciever?
Enter at Method#unbind
“Unbound methods can only be called after they are bound to an object. That object must be be a kind_of? the method’s original class.”
Ok, but what if I don’t have an instance of the class that I am copying the method from? Module#instance_method provides what we need. Please note that it does not in fact return a Method as one would expect at a first glance but an UnboundMethod since it would actually not be bound to an object.
Let’s try to bind the Numeric#twice that we defined above to String
This turns out to be a limitation in Ruby, at least MRI. Since Ruby is slightly evil, not letting us jailbreak twice, let’s bring in the heavy artillery ourselves: evil-ruby. Unfortunately this does not work with the latest MRI (1.9.3) atm, but highlights what lengths one would need to go, to achieve a solution to our problem. This makes me think that this is probably not a good idea out of a design or performance perspective.
Ok, but what CAN we do?
If we start off defining our functionality in a proc, it turns out to be quite simple to share it among classes.
But when you come to think about it, you could have an abstraction layer on top of this piece of functionality which included defining multiple methods that should be included and Voila! You have reinvented Module.
I have hopefully teased you enough to have a look at the implementation:
This is a first blogpost of many to come from me, I hope you enjoyed the tour, please join in on the discussion below