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 (
:ifand/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:
(c) Copyright 2008 Pat Nakajima, released under MIT License.