我正在升级 Rails 项目,并在早期版本中包含了 Mocha。它定义了一个
any_instance
方法。我要升级到的 Rspec 新版本还包括所有类上的 any_instance
方法。我需要在升级所有内容的同时坚持使用 Mocha 一段时间,这样我就不必更改一堆测试,所以我想使用 Mocha 版本的 any_instance
。
为了做到这一点,对于 Rspec,我首先用
disable_monkey_patching!
删除它自己的猴子补丁。然后对 Rspec 进行 config.mock_with :mocha
调用,这将导致 mocha 在 any_instance
上定义 Class
。我知道猴子修补所有课程是不好的,但这个问题实际上是一个很好的教训,说明为什么,但我对我看到的结果很好奇。
以上是关于为什么我这样做的一些背景。以下是一个最小的可重现示例,我无法解释它,希望能深入了解它。
# Define a class
class A; end
# Define a module whose class methods I'd like to include in every class
module B
module ClassMethods
def a; end
end
end
# Include method "a" in all classes
Class.send :include, B::ClassMethods
# Try it out!
A.a # <- works
现在我将使用 undef 来摆脱它,因为这就是
disable_monkey_patching!
的作用:
Class.class_exec { undef a }
A.a # <- undefined method `a' for A:Class (NoMethodError) -- that's expected
但现在我需要为
Class
定义一个不同的方法“a”,我将在模块C
中定义它。
module C
module ClassMethods
def a; end
end
end
Class.send :include, C::ClassMethods
这是让我困惑的部分:
A.a # <- undefined method `a' for A:Class (NoMethodError)
这使得
undef
看起来会永久取消定义它,但当任何人尝试定义最终无法使用的方法时,不会警告任何人。为什么会出现这种情况?
尝试过 MRI 3.2.2 和 2.7.2
undef
记录如下:
关键字阻止当前类响应 调用命名方法。undef
undef my_method
[...]
您可以在任何范围内使用
。也可以看看 模块#undef_methodundef
因此,它修改它被“调用”的模块,不仅删除定义它的方法,而且标记它,以便它永远不会响应此方法,即使它可能是在父类或其他任何地方定义的在继承链中。
(据我所知)删除此标记的唯一方法是在之前未定义该方法的模块(或类)中显式定义该方法。这是有效的,因为通过在 Class 上定义方法
a
,我们可以恢复由 undef
或 Module#undef_method
添加的“标记”
根据您的示例,可以通过以下方式实现:
class Class
def a(...)
super
end
end
在这里,我们定义了一个方法
a
,它接受任何参数,并且只调用 super
来沿着祖先链转发调用,在本例中是转发到 B::ClassMethods#a
或 C::ClassMethods#a
。
仅此一项就应该开箱即用。现在,我们甚至可以再提高一个档次,并再次删除这个新创建的方法
a
,不过这次使用 Module#remove_method
,记录为:
从当前类中删除由符号标识的方法。
这将导致一种状态,就好像该方法从未存在过(并且从未未定义):
Class.remove_method :a
最后,您可以通过在之前未定义的确切模块中定义一个新方法(使用任何接受的参数和任何方法体)来恢复
undef
和 Module#undef_method
的效果,然后再次删除它。