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 ; @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
p something.results # => [:before, :start, :bar, :finish, :after]
something = Something.new
something.foo # => :foo
something.results # => [:setup, :foo, :teardown]
something = Something.new
something. # => :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.
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:
View the CI build
(c) Copyright 2008 Pat Nakajima, released under MIT License.