Aspectory

Callbacks for Ruby.

How it works

Basically, you get three methods: before, after and around. Each of these takes a method name, then a splat args of symbols and/or a block. The symbols/block will be called before/after the method you specified.

The around callback gets passed a proc, in the form of an unnamed block for handlers that are methods and a block argument for handlers that are blocks. You must yield or call that block in order for the original method to be called.

Simple Example


require 'rubygems'
require 'aspectory'
require 'spec'

class Something
  include Aspectory::Hook

  attr_reader :results

  around :foo, :round
  before :foo, :setup
  after  :foo, :teardown

  before :bar do
    @results << :before
  end

  around :bar do |fn|
    @results << :start
    fn.call
    @results << :finish
  end

  after :bar do
    @results << :after
  end

  def initialize
    @results = []
  end

  def foo; @results << :foo; :foo end
  def bar; @results << :bar; :bar end

  def round
    @results << :start
    yield
    @results << :finish
  end

  def setup
    @results << :setup
  end

  def teardown
    @results << :teardown
  end
end

something = Something.new
p something.foo # => :foo
p something.results # => [:setup, :start, :foo, :finish, :teardown]

something = Something.new
p something.bar # => :bar
p something.results # => [:before, :start, :bar, :finish, :after]

something = Something.new
something.foo # => :foo
something.results # => [:setup, :foo, :teardown]

something = Something.new
something.bar # => :bar
something.results # => [:before, :bar, :after]

Calling Methods without Callbacks

You can use the #__PRISTINE__ method to call your methods without any callbacks, or you can just call method_name_without_callbacks. Here’s an example with the same example class we used above:


something = Something.new
something.__PRISTINE__(:foo)
something.results # => [:foo]
something.bar_without_callbacks
something.results # => [:foo, :bar]

Preventing a method from being called

If a before callback returns false, then the original method will not be called. If you want to halt the method being called, but still want to provide a return value, you can throw the name of the method:


class Something
  before :foo do
    throw :foo, "from the callback"
  end

  def foo
    "from the method"
  end
end

Something.new.foo # => "from the callback"

after callbacks get the results of the method call

Your after callbacks will be passed whatever the original method call returned:


class Something
  attr_reader :name

  after :foo do |result|
    @name = result.to_s.capitalize
  end
  
  def foo
    :foo
  end
end

something = Something.new
something.name # => nil
something.foo  # => :foo
something.name # => "Foo"

Observing method definitions

If you ever want to see when a method is defined in a class, you can register observers using the observe method. It can take either a symbol or a regular expression, then a callback block will be called when the method is defined. The callback block will be passed the name of the method defined.

To observe class method definitions, you must pass the :meta option.


class Framework
  include Aspectory
  
  # Using a symbol
  observe :admin? do
    puts "Warning! Overriding the admin method can be dangerous."
  end

  # Using a regular expression
  observe(/^_/) do |method_id|
    puts "Warning! The #{method_id} is not part of the public API!"
  end
  
  # Observing a class method definition
  observe(:find_by_name, :meta => true) do
    puts "The method :find_by_name already exists in the framework."
  end
  
  # Observing multiple occurrences of a method definition
  observe(/^show_by_/, :times => true) do |method_id|
    puts "dynamic showing defined: #{method_id}"
  end
end

Why?

Why not?

Requirements

  • nakajima gem install nakajima-nakajima --source=http://gems.github.com

TODO

  • Filters (:if and/or :unless)
  • Compilable callbacks (http://gist.github.com/50397)
  • Maybe don’t worry about @instance_eval@’ing or @instance_exec@’ing callback blocks.
  • Figure out a way to get it working with metaclasses
  • Spec suite could definitely be more readable

Alternatives:

  • http://github.com/sam/extlib/tree/master/lib/extlib/hook.rb
  • AspectR
  • Aquarium

View the CI build

(c) Copyright 2008 Pat Nakajima, released under MIT License.