我正在升级一个Rails项目,在早期版本中包含了Mocha。它定义了一个any_instance
方法。我正在升级的新版本Rspec也在所有类上包含了一个any_instance
方法。在升级所有内容的过程中,我需要坚持使用Mocha一段时间,这样我就不必更改一堆测试,所以我想使用Mocha版本的any_instance
。
要做到这一点,使用Rspec,我 * 首先 * 删除它自己的monkey与disable_monkey_patching!
的打包。然后有一个对Rspec的config.mock_with :mocha
调用,这将导致mocha在Class
上定义any_instance
。我知道monkey修补所有类是不好的,但这个问题实际上是一个很好的教训,为什么,但我对我看到的结果很好奇。
以上是我为什么要这么做的一些背景。下面是一个最小的可重复的例子,我不能解释,希望你能有深入的了解。
# 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上尝试
2条答案
按热度按时间xvw2m8pv1#
调用
include
不会将方法复制到接收方,它只是将包含的模块添加到方法查找所遍历的模块列表中。我们可以通过检查
A
的singleton类的ancestors
来看到这个列表:字符串
当将
B::ClassMethods
包含到Class
中时,此列表会相应更改:型
请注意,
B::ClassMethods
是在 *Class
之后 * 添加的。现在,如果您通过
undef
/undef_method
“取消定义”方法a
,并将Class
作为其接收器,则会导致Class
通过引发(人工)NoMethodError
来阻止对该方法的调用,这也会结束进一步的方法查找:(我说“人工”是因为该方法仍然存在)型
如果在
Class
中包含另一个模块C::ClassMethods
,它将被添加到列表中,在B::ClassMethods
之前,但仍然在 *Class
之后:型
由于
Class
仍然会阻止a
被调用,因此也无法访问新的a
方法:型
对于实际问题(Mocha),您应该首先检查对象的祖先,并确定两个
any_instance
方法的定义位置。然后,您可以通过
include
/prepend
将猴子补丁添加到正确的位置。i2byvkas2#
undef
记录如下:undef
关键字阻止当前类响应对命名方法的调用。字符串
[...]
您可以在任何作用域中使用
undef
。参见模块#undef_method因此,它修改了它被“调用”的模块,不仅删除了它定义的方法,而且标记了它,使它永远不会响应这个方法,即使它可能是在父类或继承链中的任何其他地方定义的。
唯一的方法(我知道)删除这个标记是在模块(或类)中显式定义一个方法,在此之前未定义该方法。这是因为通过在Class上定义方法
a
,我们恢复了undef
或Module#undef_method
添加的“标记”。以你的例子为例,这可以通过以下方式实现:
型
在这里,我们定义了一个方法
a
,它接受任何参数,只调用super
来将调用沿着祖先链转发,在本例中,要么转发到B::ClassMethods#a
,要么转发到C::ClassMethods#a
。现在,我们甚至可以将其添加到一个新的级别,并再次删除这个新创建的方法
a
,但是这次使用Module#remove_method
,它被记录为:从当前类中删除由符号标识的方法。
这将导致一种状态,好像该方法从未存在过(并且从未定义过):
型
最后,您可以通过在先前未定义的Module中定义一个新方法(带有任何可接受的参数和任何方法体),然后再次删除它来恢复
undef
和Module#undef_method
的效果。