我正在尝试包装TestClass
的所有实例方法,以在调用 之前和 之后执行代码。到目前为止,此代码有效:
module Wrapper
def wrap(*methods)
prependable_module = Module.new do
methods.each do |m|
define_method(m) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
end
prepend prependable_module
end
end
class TestClass
extend Wrapper
wrap :instance_method1
def instance_method1
p 2
end
end
TestClass.new.instance_method1 # => 1, 2, 3
我可以使用所有方法名称作为参数来调用wrap
。如果我尝试包装所有方法而不单独列出它们,则需要使用类定义底部的instance_methods(false)
进行调用。
class TestClass
extend Wrapper
def instance_method1
p 2
end
wrap(*instance_methods(false))
end
在Rails中,通常在类定义的顶部调用诸如before_action
或after_create
之类的所有回调方法。 我的目标是也要在类定义的顶部调用wrap
(不单独列出所有方法)。在这种情况下,我无法在类定义的顶部调用instance_methods(false)
,因为此时尚未定义任何方法。
感谢您的帮助!
借助Kimmo Lehto's方法,我可以使用method_added
钩子包装每个实例方法。我不想为每个定义的方法添加一个新模块,所以我将所有重写的方法添加到同一模块中。
module Wrapper
def method_added(method_name)
tmp_module = find_or_initialize_module
return if tmp_module.instance_methods(false).include?(method_name)
tmp_module.define_method(method_name) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
def find_or_initialize_module
module_name = "#{name}Wrapper"
module_idx = ancestors.map(&:to_s).index(module_name)
unless module_idx
prepend Object.const_set(module_name, Module.new)
return find_or_initialize_module
end
ancestors[module_idx]
end
end
class TestClass
extend Wrapper
def instance_method1
p 2
end
end
tc = TestClass.new
tc.instance_method1 # => 1, 2, 3
答案 0 :(得分:1)
您可以使用Module#method_added挂钩自动包装添加的所有方法。
您将需要一些魔术来避免无限循环导致堆栈溢出。
另一种选择是一旦定义了类,就使用TracePoint来触发包装。您可以使用Module#extended
来设置跟踪点。像这样:
module Finalizer
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.finalize
t.disable
end
end
end
def finalize
wrap(*instance_methods(false))
end
end
除非明确地.freeze
,否则类通常不是完全“关闭”的,因此这是一个有点棘手的解决方案,并且如果以后添加方法也不会触发。 method_added
可能是您最好的选择。