Created

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

Creates before/after hooks for methods

View hooks.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
# a response to www.ruby-forum.com/topic/4405076
 
module Hook
def before(meth_name, &callback)
add_hook :before, meth_name, &callback
end
def after(meth_name, &callback)
add_hook :after, meth_name, &callback
end
def hooks
@hooks ||= Hash.new do |hash, method_name|
hash[method_name] = { before: [], after: [], hijacked: false }
end
end
def add_hook(where, meth_name, &callback)
hooks[meth_name][where] << callback
ensure_hijacked meth_name
end
def method_added(meth_name)
ensure_hijacked meth_name if hooks.has_key? meth_name
end
def ensure_hijacked(meth_name)
return if hooks[meth_name][:hijacked] || !instance_methods.include?(meth_name)
meth = instance_method meth_name
_hooks = hooks
_hooks[meth_name][:hijacked] = true
define_method meth_name do |*args, &block|
_hooks[meth_name][:before].each &:call
return_value = meth.bind(self).call *args, &block
_hooks[meth_name][:after].each &:call
return_value
end
end
end
 
describe Hook do
let :klass do
Class.new do
extend Hook
singleton_class.extend Hook
def self.meth(seen)
seen << "meth"
end
def meth(seen)
seen << "meth"
end
end
end
let(:instance) { klass.new }
let(:array) { [] }
it 'can be applied before and after instance methods' do
klass.before(:meth) { array << "before" }
klass.after(:meth) { array << "after" }
instance.meth array
array.should == %w[before meth after]
end
it 'can be applied before and after class methods' do
klass.singleton_class.before(:meth) { array << 'before' }
klass.singleton_class.after(:meth) { array << 'after'}
klass.meth array
array.should == %w[before meth after]
end
specify 'the method gets the params correctly and returns the return value correctly' do
klass.class_eval { def meth(*args, &block) [args, block] end }
klass.before(:meth) { }
klass.after(:meth) { }
return_val = instance.meth(1, 2, 3) { 10 }
return_val.first.should == [1, 2, 3]
return_val.last.call.should == 10
end
specify 'multiple before/after blocks can be declared and are executed in the order of declaration' do
klass.before(:meth) { array << "before1" }
klass.before(:meth) { array << "before2" }
klass.after(:meth) { array << "after1" }
klass.after(:meth) { array << "after2" }
instance.meth array
array.should == %w[before1 before2 meth after1 after2]
end
specify "instance and class methods don't conflict" do
klass.before(:meth) { array << "before instance" }
klass.singleton_class.extend Hook
klass.singleton_class.before(:meth) { array << 'before class' }
klass.meth array
instance.meth array
array.should == ['before class', 'meth', 'before instance', 'meth']
end
specify 'hooks can be before or after method definitions' do
array = []
klass.class_eval do
before(:meth2) { array << 'before1' }
after(:meth2) { array << 'after1' }
define_method(:meth2) { array << 'meth' }
before(:meth2) { array << 'before2' }
after(:meth2) { array << 'after2' }
end
klass.new.meth2
array.should == %w[before1 before2 meth after1 after2]
end
end
Owner

Just realized this probably doesn't work with subclasses / method redefinitions, but don't have time right now to look into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.