为什么我在 ruby 中使用 undef 取消定义包含的类方法后无法重新定义它?

问题描述 投票:0回答:1

我正在升级 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

ruby module rspec ruby-mocha
1个回答
0
投票

undef
记录如下:

undef
关键字阻止当前类响应 调用命名方法。

undef my_method

[...]

您可以在任何范围内使用

undef
。也可以看看 模块#undef_method

因此,它修改它被“调用”的模块,不仅删除定义它的方法,而且标记它,以便它永远不会响应此方法,即使它可能是在父类或其他任何地方定义的在继承链中。

(据我所知)删除此标记的唯一方法是在之前未定义该方法的模块(或类)中显式定义该方法。这是有效的,因为通过在 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
的效果,然后再次删除它。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.