2

我在我的代码中遇到了一个奇怪的错误。我有一个rails应用程序,在lib中有以下两个文件:

lib/module_one/module_two/class_one.rb

module ModuleOne
  module Moduletwo
    class ClassOne
      class << self
        def test
          puts 'Class one'
          ClassTwo.test
        end
      end
    end
  end
end

lib/module_one/module_two/class_two.rb

module ModuleOne
  module ModuleTwo
    class ClassTwo
      def self.test
        puts 'Class two'
      end
    end
  end
end

现在我的问题是,当我进入控制台并编写时:

ModuleOne::ModuleTwo::ClassOne.test

它抛出以下内容:NameError: uninitialized constant ClassTwo

奇怪的是,这个问题似乎与使用 ofclass << self而不是self.method. 如果我像这样更改 class_one.rb 文件,它可以工作!:

module ModuleOne
  module ModuleTwo
    class ClassOne
      def self.test
        puts 'Class one'
        ClassTwo.test
      end
    end
  end
end

我在 application.rb 中加载文件,如下所示:

config.autoload_paths += %W(#{config.root}/lib)

这是rails中的错误,还是只是我弄错了?

我正在使用 rails 3.1.3 btw

4

2 回答 2

9

不断查找

所以首先,常量解析的基本过程是 ruby​​ 首先搜索接收者的词法范围(包含引用的类或模块)ClassTwo,当它找不到它时,它会上升一个级别(Module.nesting返回这个搜索路径) 等等。在您的情况下,这意味着寻找ModuleOne::ModuleTwo::ClassOne:ClassTwo, then ModuleOne::ModuleTwo::ClassTwo, thenModuleOne::ClassTwo等等。

如果失败,ruby 会在封闭类/模块的继承层次结构中查找(例如,ClassOne 的超类定义了一些东西。ancestors模块上的方法返回此搜索路径。最后,搜索顶级常量。

Rails 自动加载

回到 Rails 的神奇加载。在这里const_missing,rails 添加了一个钩子,当 ruby​​ 找不到该类时调用该钩子,它基本上试图复制这个搜索逻辑,在每个步骤中查看是否可以加载包含丢失常量的文件。

理想情况下,ruby 会通过搜索路径(即嵌套)进行搜索,但不幸的是它没有:当您引用 时ClassTwoconst_missing仅使用“ClassTwo”调用。

Rails 通过在其前面加上const_missing被调用的类的名称来猜测嵌套(即包含对常量的访问的类)。例如,在您的第二个示例中,它以ModuleOne::ModuleTwo::ClassOne::ClassTwo. 您可以通过定义const_missing记录它所调用的内容来很容易地看到这一点

class Object
  def self.const_missing missing_name
    puts "qualified name is #{self.name}::#{missing_name}"
    super
  end
end

然后 Rails 剥离“ClassOne”并尝试ModuleOne::ModuleTwo::ClassTwo等等。

那么为什么会class << self有所作为呢?如果您使用const_missing日志记录重复第一个案例,您会看到记录的限定名称现在只是::ClassTwo. const_missing现在正在 ClassOne 的元类上调用,并且由于class << self尚未分配给常量,因此它没有名称,因此 rails 试图捏造嵌套的尝试不起作用。

这为一个可怕的解决方法打开了大门:

module ModuleOne
  module ModuleTwo
    class ClassOne
      class << self
        def test
          puts 'Class one'
          ClassTwo.test
        end
      end
      FOO = class << self; self; end
    end
  end
end

因为现在调用 const_missing 的类有一个名称 (ModuleOne::ModuleTwo::ClassOne::FOO),所以现在可以使用 rails 的解决方法。

我认为戴夫的解决方法有效,因为调用了 const_missingModuleOne::ModuleTwo::ClassOne而不是匿名特征类/元类。

真正的解决方法是让红宝石通过const_missing嵌套。有一个针对 ruby​​ 的错误记录,尽管它已经打开了很长时间。所以是的,这可能被认为是魔法加载中的一个错误(还有其他边缘情况),但根本原因是 ruby​​ api 中的一个弱点,它强制使用脆弱的解决方法。

于 2012-05-17T10:02:02.263 回答
1

(只是部分答案,但需要格式化。)

这是因为如何class << self工作。

例如,如果您将其更改为:

class << self
  def test
    self::ClassTwo.test
  end
end

它工作正常。


编辑; 太长的合理评论。

我正在四处寻找......在直观的层面上,这对我来说是有意义的,我只是不知道为什么。不知道我是否知道一个真正的原因,或者我只是在编造。

不过,我不确定为什么self似乎是指该模块;“Programming Ruby 1.9”一书没有深入探讨class <<语义。我会发推文并参考这个问题,更聪明的人会创建一个真正的答案。

于 2012-05-16T13:27:11.977 回答