I'm trying to get a callback when any method on a particular class is called. Overriding "send" doesn't work. It seems send doesn't get called in normal Ruby method invocation. Take the following example.
class Test
def self.items
@items ||= []
end
end
If we override send on Test, and then call Test.items, send doesn't get called.
Is what I'm trying to do possible?
I'd rather not use set_trace_func, since it'll probably slow down things considerably.
Use alias
or alias_method
:
# the current implementation of Test, defined by someone else
# and for that reason we might not be able to change it directly
class Test
def self.items
@items ||= []
end
end
# we open the class again, probably in a completely different
# file from the definition above
class Test
# open up the metaclass, methods defined within this block become
# class methods, just as if we had defined them with "def self.my_method"
class << self
# alias the old method as "old_items"
alias_method :old_items, :items
# redeclare the method -- this replaces the old items method,
# but that's ok since it is still available under it's alias "old_items"
def items
# do whatever you want
puts "items was called!"
# then call the old implementation (make sure to call it last if you rely
# on its return value)
old_items
end
end
end
I rewrote your code using the class << self
syntax to open up the metaclass, because I'm not sure how to use alias_method
on class methods otherwise.
Something like this: works with instance methods and class methods, it will not only intercept the current methods defined in the class but any that are added later though reopening the class etc.
(there is also rcapture http://code.google.com/p/rcapture/):
module Interceptor
def intercept_callback(&block)
@callback = block
@old_methods = {}
end
def method_added(my_method)
redefine self, self, my_method, instance_method(my_method)
end
def singleton_method_added(my_method)
meta = class << self; self; end
redefine self, meta, my_method, method(my_method)
end
def redefine(klass, me, method_name, my_method)
return unless @old_methods and not @old_methods.include? method_name
@old_methods[method_name] = my_method
me.send :define_method, method_name do |*args|
callback = klass.instance_variable_get :@callback
orig_method = klass.instance_variable_get(:@old_methods)[method_name]
callback.call *args if callback
orig_method = orig_method.bind self if orig_method.is_a? UnboundMethod
orig_method.call *args
end
end
end
class Test
extend Interceptor
intercept_callback do |*args|
puts 'was called'
end
def self.items
puts "items"
end
def apple
puts "apples"
end
end
class Test
def rock
puts "rock"
end
end
Test.items
Test.new.apple
Test.new.rock
You can see how this is done via the ExtLib hook functionality. ExtLib::Hook basically allows you to invoke arbitrary callbacks before or after a method is completed. See the code on GitHub here for how its done (it overrides :method_added
to automagically rewrite methods as they're added to the class).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With