Doing Aspect-Oriented Programming


You want to "wrap" a method with new code, so that calling the method triggers some new feature in addition to the original code.


You can arrange for code to be called before and after a method invocation by using method aliasing and metaprogramming, but it's simpler to use the glue gem or the AspectR third-party library. The latter lets you define "aspect" classes whose methods are called before and after other methods.

Here's a simple example that traces calls to specific methods as they're made:

	require 'aspectr'
	class Verbose < AspectR::Aspect

	 def describe(method_sym, object, *args)

	 def before(method_sym, object, return_value, *args)
	 puts "About to call #{describe(method_sym, object, *args)}."

	 def after(method_sym, object, return_value, *args)
	 puts "#{describe(method_sym, object, *args)} has returned " +
	 return_value.inspect + '.'

Here, I'll wrap the push and pop methods of an array. Every time I call those methods, the aspect code will run and some diagnostics will be printed.

	verbose =
	stack = []
	verbose.wrap(stack, :before, :after, :push, :pop)

	# About to call [].push(10).
	# [10].push(10) has returned [[10]].

	# About to call [10].push(4).
	# [10, 4].push(4) has returned [[10, 4]].

	# About to call [10, 4].pop().
	# [10].pop() has returned [4].



There's a pattern that shows up again and again in Ruby (we cover it in Recipe 7.10). You write a method that performs some task-specific setup (like initializing a timer), runs a code block, then performs task-specific cleanup (like stopping the timer and printing out timing results). By passing in a code block to one of these methods you give it a new aspect: the same code runs as if you'd just called Proc#call on the code block, but now it's got something extra: the code gets timed, or logged, or won't run without authentication, or it automatically performs some locking.

Aspect-oriented programming lets you permanently add these aspects to previously defined methods, without having to change any of the code that calls them. It's a good way to modularize your code, and to modify existing code without having to do a lot of metaprogramming yourself. Though less mature, the AspectR library has the same basic features of Java's AspectJ.

The Aspect#wrap method modifies the methods of some other object or class. In the example above, the push and pop methods of the stack are modified: you could also modify the Array#push and Array#pop methods themselves, by passing in Array instead of stack.

Aspect#wrap aliases the old implementations to new names, and defines the method anew to include calls to a "pre" method (@Verbose#before in the example) and/or a "post" method (@Verbose#after in the example).

You can wrap the same method with different aspects at the same time:

	class EvenMoreVerbose < AspectR::Aspect
	 def useless(method_sym, object, return_value, *args)
	 puts "More useless verbosity."

	more_verbose =
	more_verbose.wrap(stack, :useless, nil, :push)
	# About to call [10].push(60).
	# More useless verbosity.
	# [10, 60].push(60) has returned [[10, 60]].

You can also undo the effects of a wrap call with Aspect#unwrap.

	verbose.unwrap(stack, :before, :after, :push, :pop)
	more_verbose.unwrap(stack, :useless, nil, :push)
	stack.push(100) # => [10, 60, 100]

Because they use aliasing under the covers, you can't use AspectR or glue to attach aspects to operator methods like <<. If you do, AspectR (for instance) will try to define a method called __aop__singleton_<<, which isn't a valid method name. You'll need to do the alias yourself, using a method name like "old_lshift", and define a new << method that makes the pre- and post-calls.

See Also

  • The AspectR home page is at
  • Recipe 7.10, "Hiding Setup and Cleanup in a Block Method"
  • Recipe 10.14, "Aliasing Methods"
  • Recipe 20.4, "Synchronizing Access to an Object"

Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399
Simiral book on Amazon © 2008-2017.
If you may any questions please contact us: