好的,所以我设法通过一些元编程来实现这一点。首先定义这个模块:
module MetaprogrammingStuff
def rescue_all_methods(klass, *errors_to_rescue, &blk)
patch_all_methods(klass) do |orig_method, *args, &caller_blk|
begin
orig_method.call *args, &caller_blk
rescue *errors_to_rescue => e
blk.call e
end
end
end
def patch_all_methods(klass, &blk)
patch_class_methods(klass, &blk)
patch_instance_methods(klass, &blk)
end
def patch_class_methods(klass, &blk)
klass.methods(false).each do |fn_name|
orig_method = klass.method fn_name
klass.singleton_class.send(:define_method, fn_name) do |*args, &caller_blk|
blk.call orig_method, *args, &caller_blk
end
end
end
def patch_instance_methods(klass, &blk)
klass.instance_methods(false).each do |fn_name|
orig_method = klass.instance_method fn_name
klass.send(:define_method, fn_name) do |*args, &caller_blk|
blk.call orig_method.bind(self), *args, &caller_blk
end
end
end
end
然后将其扩展为一个类并运行rescue_all_methods:
class TestClass
extend MetaprogrammingStuff
def foo
raise ArgumentError
end
def self.foo
raise RuntimeError
end
rescue_all_methods(self, ArgumentError, RuntimeError) do |error|
puts "#{error.class} was raised."
end
end
在定义方法之后调用rescue_all_methods 很重要。
您可以看到它在所有实例和类方法周围插入了一个救援块:
TestClass.foo
# => RuntimeError was raised.
TestClass.new.foo
# => ArgumentError was raised.
它是如何工作的 - 基本上它使用 method 和 instance_method 方法将方法转换为匿名函数,然后将这些方法名称重新分配给调用原始方法但使用 begin..rescue 包装器的新方法。