Metaprogramming.apply(Aspects)
Posted by Mathew Abonyi Wed, 10 Oct 2007 02:51:00 GMT
I’m just briefly breaking my silence (which must continue a bit longer) to raise awareness about an impressive and very useful gem which has just come onto the scene: Aquarium.
Although I don’t follow Aspect Oriented Programming very closely, I know about its benefits and use it when necessary. All Rails developers, for example, employ its principles without even knowing it: before, after and around filters, proxies to alter execution, callbacks and the like. If you don’t know about AOP yet, basically it’s the practice of separating components which appear across the various classes of an application to provide system or class-agnostic functionality—for example, logging, authentication or object persistence.
Having these components intrude on, for example, calculating a bank balance really gets annoying after a while. Imagine every moderately sized script you write needing lots of diagnostic messages, custom event handlers, and the like. It gets very tedious because you know there’s the ideal of reuse just around the corner.
Clean, Ruby-esque AOP… a Pipe Dream?
Imagine methods without calls to a logger, without a litter of ‘if/then/else’ for authentication or authorisation, without calls to save an object and checking if it returns true or false… What you’ll get is the raw functionality of your class or module, which… umm, is as it should be. Wrapping a method (using cuts, wrapping or aspects from AOP) lets you extract common functionality which cuts across objects.
Until now, there’s only been the Facets libraries for AOP (‘advice’, ‘aop’ and ‘cut’ in Facets 2.0.0) and AspectR. The former is buggy at best and the latter is like returning to Java. So I wasn’t very keen on AOP in Ruby at all. Metaprogramming seemed easy enough and didn’t need the structure of AOP to follow.
Enter Aquarium. It’s a glass of clean water in a desert. Ruby-style. The most important thing it offers is a ‘Join Point Model’ which will make you wet. In other words, it is very easy to intercept method calls to wrap them and introduce functionality before or after the original definition.
Sadly, I don’t have the time for an extensive example from my own coding, so I’ll have to rip off the example from Aquarium’s own README which I think illustrates its power best (with a few modifications to show how I use it at the moment): method tracing.
Example: Method Tracing
require 'rubygems'
require 'aquarium'
require 'aquarium/aspects/dsl/object_dsl' # makes the Aspects DSL (before/after/around) system-wide
module Example
class Foo
def initialize *args
p "Inside: Foo#initialize: args = #{args.inspect}"
end
def do_it *args
p "Inside: Foo#do_it: args = #{args.inspect}"
end
end
module BarModule
def initialize *args
p "Inside: BarModule#initialize: args = #{args.inspect}"
end
def do_something_else *args
p "Inside: BarModule#do_something_else: args = #{args.inspect}"
end
end
class Bar
include BarModule
end
end
p "Before advising the methods:"
foo1 = Example::Foo.new :a1, :a2
foo1.do_it :b1, :b2
bar1 = Example::Bar.new :a3, :a4
bar1.do_something_else :b3, :b4
# EXAMPLE 1: Wrapping methods on multiple classes using the powerful 'types' matcher.
# It can take constants or regular expressions. Also demonstrates method_options, which
# can specify public, protected, private, inherited or local methods.
module Example
include Aquarium::Aspects::DSL::AspectDSL
around :types => [Foo, Bar], :methods => :all,
:method_options => :suppress_ancestor_methods do |jp, *args|
begin
p "Ex. 1: Entering: #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
jp.proceed
ensure
p "Ex. 1: Leaving: #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
end
end
end
# EXAMPLE 2: Wrapping one or more methods very simply.
module Example
class Bar
after :do_something_else do |jp, *args|
p "Ex. 2: Simply after: #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
end
end
end
# EXAMPLE 3: Using the Aspect class without the DSL
include Aquarium::Aspects
Aspect.new :before, :method => :do_it, :type => Example::Foo do |jp, *args|
p "Ex. 3: Simply before (using Aspect.new): #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
end
# EXAMPLE 4: Contain aspects within a namespace for clarity. These are executed
# upon loading the source.
module Example
module AllAspects
include Aquarium::Aspects::DSL::AspectDSL
before :do_it, :type => Example::Foo do |jp, *args|
p "Ex. 4: Simply before (using namespaces): #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
end
end
end
# And now see the result...
puts
p "After advising the methods. Notice that #intialize isn't advised:"
foo2 = Example::Foo.new :a5, :a6
foo2.do_it :b5, :b6
bar1 = Example::Bar.new :a7, :a8
bar1.do_something_else :b7, :b8
You could argue that the namespace for Aquarium needs a little work and the availability of Aspect.new system wide should be a given, but adding that oneself until Aquarium has it is no big deal. I also didn’t demonstrate pointcuts, another option to around, before and after. With a pointcut, you can encapsulate the type, scope and method names—perhaps as a constant, class variable or class method to reduce dependencies.
Overview
All in all, the DSL is already miles better than anything else out there and Aquarium was only released to the public months ago. To summarise, I think this DSL is killer because it is…
- clear about what is affected
- easy to separate from what is affected
- rich with selectors (type, scope, name, pointcut)
- able to choose whether to alter method execution
So, I would definitely suggest giving it a go and seeing how it can modularise some of your ‘cross cutting concerns’. I also suggest the Aquarium README and the reading recommended on its site, http://aquarium.rubyforge.org/.

Was this silence aforementioned?
Oh well.
I’ve always enjoyed your blogs… and creations.
Will continue to keep you in the feed reader.
I am still around and watching the blog - or at least making sure it is alive and up - but there just hasn’t been the time to post anything.
There have been a lot of utilities which I’ve noticed and would like to post about; a lot of techniques I’ve learned over the last year; a lot of changes to my own code that I’d like to share. It’s just about finding the time.
Thanks for the support—I’ll try to be more vocal in the near future when I’ve freed up some time. I think realistically it’ll be about a month from now before I’m properly back, though.
Things I’m considering posting about: