public
Created

Creates before/after hooks for methods

  • Download Gist
hooks.rb
Ruby
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

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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.