Ruby AOP Made Simple
October 13, 2010
Ruby AOP Made Simple
Quick Recap
Aspect Oriented Programming (AOP) is a topic I have spent a fair amount of
time contemplating. I, along with Peter VanBroekhoven, developed
the concept of Cut-based AOP back in 2005-2006. A limited “toy” implementation of
which can be had by installing the cuts gem. The basic idea behind Cut-based
AOP is the transparent subclass, a.k.a. the cut, which is essentially
a subclass that subsumes the role of the class it effects without the programmer
needing any knowledge of it doing do. In this way, the cut serves as the atomic
construct in a purely object-oriented appraoch to AOP. You can read more about it
here.
In dicussing this idea on the ruby-talk mailing list it was suggested that an easier approach would be to forgo the Cut class and simply allow modules to be “prepended” to the class or module to which they are applied. So for instance we might write:
class C
def x(s); "#{s}" ; end
end
module A
def x(s); '{' + s + '}' ; end
end
class C
prepend A
end
C.new.x('hello') #=> "{hello}"
Cut-based AOP is a general OOP design that can be applied to any
object-oriented programming language. But for Ruby, the idea of prepend,
while more limited, does serve much of the same purpose, and the idea is up for
consideration in a future verison of Ruby.
Another Way
There is however another way to essentially use prepend-like AOP in Ruby without extending Ruby in any special way. The trick is simply to design classes and module to be “AOP-ready”. Here is an example of the above using nothing more than standard Ruby.
class C
module Joinable
def x(s); "#{s}" ; end
end
include Joinable
end
module A
def x(s); '{' + s + '}' ; end
end
class C
include A
end
C.new.x('hello') #=> "{hello}"
Pretty easy. We have simply encapsulate C’s instance methods in a “Joinable” module, thus any new inclusions into C itself will actually come before these methods.
This of course raises the issue of including modules in the normal fashion, in which case we would need in include them in Joinable itself, e.g.
class C
module Joinable
include Foo
end
end
We could facilitate this will a little bit of Ruby magic.
class Module
alias_method :_include, :include
def include(*mods)
if const_defined?(:Joinable)
core = const_get(:Joinable)
core._include(*mods)
else
_include(*mods)
end
end
def prepend(*mods)
if const_defined?(:Joinable)
_include(*mods)
else
raise "#{self} is not Joinable"
end
end
end
This is a very simplistic implementation, but a robust implementation would
be only slightly more complex. Now, as along as we use Joinable,
we can use #prepend.