我正在尝试在调用特定类的任何方法时获得回调。 覆盖“发送”不起作用。似乎发送不会在正常的Ruby方法调用中调用。以下面的例子为例。
class Test
def self.items
@items ||= []
end
end
如果我们覆盖Test on Test,然后调用Test.items,则不会调用send。
我正在尝试做什么?
我宁愿不使用set_trace_func,因为它可能会大大减慢速度。
答案 0 :(得分:12)
使用alias
或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
我使用class << self
语法重写了您的代码以打开元类,因为我不确定如何在类方法上使用alias_method
。
答案 1 :(得分:4)
这样的事情:使用实例方法和类方法,它不仅会拦截类中定义的当前方法,还会拦截稍后通过重新打开类等添加的任何方法。
(还有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
答案 2 :(得分:2)
您可以通过ExtLib挂钩功能查看这是如何完成的。 ExtLib::Hook基本上允许您在方法完成之前或之后调用任意回调。请参阅GitHub上的代码,了解其完成情况(它会覆盖:method_added
,以便在将类添加到类中时自动重写方法。)
答案 3 :(得分:1)
你可以做这样的事情,你甚至可以在被调用的方法上设置条件(我不认为这是有用的,但你仍然只是为了以防万一)。
module MethodInterceptor
def self.included(base)
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
# we declare the method_list on the class env
@_instance_method_list = base.instance_methods.inject(Hash.new) do |methods, method_name|
# we undef all methods
if !%w(__send__ __id__ method_missing class).include?(method_name)
methods[method_name.to_sym] = base.instance_method(method_name)
base.send(:undef_method, method_name)
end
methods
end
end
end
module ClassMethods
def _instance_method_list
@_instance_method_list
end
def method_added(name)
return if [:before_method, :method_missing].include?(name)
_instance_method_list[name] = self.instance_method(name)
self.send(:undef_method, name)
nil
end
end
module InstanceMethods
def before_method(method_name, *args)
# by defaults it always will be called
true
end
def method_missing(name, *args)
if self.class._instance_method_list.key?(name)
if before_method(name, *args)
self.class._instance_method_list[name].bind(self).call(*args)
else
super
end
else
super
end
end
end
end
class Say
include MethodInterceptor
def before_method(method_name, *args)
# you cannot say hello world!
return !(method_name == :say && args[0] == 'hello world')
end
def say(msg)
puts msg
end
end
希望这有效。
答案 4 :(得分:1)
gem install rcapture
可以在here
找到介绍性文章require 'rcapture'
class Test
include RCapture::Interceptable
end
Test.capture_post :class_methods => :items do
puts "items!"
end
Test.items
#=> items!
答案 5 :(得分:0)
这就是你想要的,RCapture:http://cheind.wordpress.com/2010/01/07/introducing-rcapture/
答案 6 :(得分:0)
我没有完整的答案,但我认为method_added可能对此有所帮助。
答案 7 :(得分:0)
我使用Proxy类工作 - 然后使用真实类的名称设置常量。我不知道如何让它与实例一起工作。有没有办法改变哪些对象变量也指向?
基本上,我想这样做:
t = Test.new
Persist.new(t)
t.foo # invokes callback
以下是我用它来处理类的代码:
class Persist
class Proxy
instance_methods.each { |m|
undef_method m unless m =~ /(^__|^send$|^object_id$)/
}
def initialize(object)
@_persist = object
end
protected
def method_missing(sym, *args)
puts "Called #{sym}"
@_persist.send(sym, *args)
end
end
attr_reader :object, :proxy
def initialize(object)
@object = object
@proxy = Proxy.new(@object)
if object.respond_to?(:name)
silence_warnings do
Object.const_set(@object.name, @proxy)
end
end
end
end
答案 8 :(得分:0)
我的方法是将我正在尝试使用Logger shell对象的对象包装起来,该对象只是回调原始对象。下面的代码通过使用一个类来包装您想要记录的对象,该类只调用底层对象上您想要的任何方法,但提供了一种方法来捕获这些调用并记录每个访问事件(或其他任何事件)。
class Test
def self.items
puts " Class Items run"
"Return"
end
def item
puts " Instance item run"
return 47, 11
end
end
class GenericLogger
@@klass = Object # put the class you want to log into @@klass in a sub-class
def initialize(*args)
@instance = @@klass.new(*args)
end
def self.method_missing(meth, *args, &block)
retval = handle_missing(@@klass, meth, *args, &block)
if !retval[0]
super
end
retval[1]
end
def method_missing(meth, *args, &block)
retval = self.class.handle_missing(@instance, meth, *args, &block)
if !retval[0]
super
end
retval[1]
end
def self.handle_missing(obj, meth, *args, &block)
retval = nil
if obj.respond_to?(meth.to_s)
# PUT YOUR LOGGING CODE HERE
if obj.class.name == "Class"
puts "Logger code run for #{obj.name}.#{meth.to_s}"
else
puts "Logger code run for instance of #{obj.class.name}.#{meth.to_s}"
end
retval = obj.send(meth, *args)
return true, retval
else
return false, retval
end
end
end
# When you want to log a class, create one of these sub-classes
# and place the correct class you are logging in @@klass
class TestLogger < GenericLogger
@@klass = Test
end
retval = TestLogger.items
puts "Correctly handles return values: #{retval}"
tl = TestLogger.new
retval = tl.item
puts "Correctly handles return values: #{retval}"
begin
tl.itemfoo
rescue NoMethodError => e
puts "Correctly fails with unknown methods for instance of Test:"
puts e.message
end
begin
TestLogger.itemsfoo
rescue NoMethodError => e
puts "Correctly fails with unknown methods for class Test"
puts e.message
end
该代码示例的输出是:
Logger code run for Test.items
Class Items run
Correctly handles return values: Return
Logger code run for instance of Test.item
Instance item run
Correctly handles return values: [47, 11]
Correctly fails with unknown methods for instance of Test:
undefined method `itemfoo' for #<TestLogger:0x2962038 @instance=#<Test:0x2962008>>
Correctly fails with unknown methods for class Test
undefined method `itemsfoo' for TestLogger:Class
答案 9 :(得分:0)
singleton_method_added
可以为您提供简单的解决方案:
class Item
@added_methods = []
class << self
def singleton_method_added name
if name != :singleton_method_added && !@added_methods.include?(name)
@added_methods << name
pMethod = self.singleton_method name
self.singleton_class.send :define_method, name do |*args, &blk|
puts "Callback functions calling..."
pMethod.call(*args, &blk)
end
end
end
def speak
puts "This is #{self}"
end
end
希望这会有所帮助。