有人可以帮我修改为intercepting instance method calls提供的答案,以便它可以使用类方法调用,还是类和实例方法调用?根据我对Ruby的元编程知识的有限知识,我想它可能与使用class << self
在某个地方打开单例类有关,但是我已经尝试用这个代码在各个地方做这个,我可以似乎没弄明白。但是,如果不是直接回答,你能否为我提供正确的方向?除非我完全超出自己的深度,否则我很喜欢为自己搞清楚事情。谢谢!
答案 0 :(得分:2)
以下是我根据您提供的链接中的答案修改的解决方案。我将钩子逻辑从超类移到了一个单独的模块,这样当一个类需要钩子时,它只需要包含或扩展该模块并调用钩子方法。
before_each_method type, &block
- type
可以是:class
或:instance
,该块是每个方法之前要执行的代码。该块将在某些环境下进行评估,即例如方法,块中的self
是实例;对于类方法,块中的self
是类。before_class_method &block
- before_each_method :class, &block
before_instance_method &block
- before_each_method :instance, &block
module MethodHooker
def self.included(base)
base.extend(ClassMethods)
end
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_each_method type, &block
singleton = class << self; self; end
case type
when :instance
this = self
singleton.instance_eval do
define_method :method_added do |name|
last = instance_variable_get(:@__last_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:@__last_methods_added, [name, with, without])
this.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:@__last_methods_added, nil)
end
end
when :class
this = self
singleton.instance_eval do
define_method :singleton_method_added do |name|
return if name == :singleton_method_added
last = instance_variable_get(:@__last_singleton_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:@__last_singleton_methods_added, [name, with, without])
singleton.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:@__last_singleton_methods_added, nil)
end
end
end
end
def before_class_method &block
before_each_method :class, &block
end
def before_instance_method &block
before_each_method :instance, &block
end
end
end
class Test
extend MethodHooker
before_each_method :instance do |method, args, block|
p [method, args, block]
puts "before instance method(#{method}) #{@var}"
end
before_class_method do |method, args, block|
puts "before class method(#{method}) #{@class_instance_var}"
end
@class_instance_var = 'stackoverflow'
def initialize
@var = 1
end
def test(a, b, c)
puts "instance method test"
end
def self.test1
puts "class method test"
end
end
Test.new.test(1, "arg2", [3]) {|t| t}
Test.test1
输出类似于:
[:initialize, [], nil]
before instance method(initialize)
[:test, [1, "arg2", [3]], #<Proc:0x00000001017d5eb8@/Users/test/before_method.rb:88>]
before instance method(test) 1
instance method test
before class method(test1) stackoverflow
class method test