我在代码中遇到了一个奇怪的错误.我有一个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
Run Code Online (Sandbox Code Playgroud)
和
LIB/module_one/module_two/class_two.rb
module ModuleOne
module ModuleTwo
class ClassTwo
def self.test
puts 'Class two'
end
end
end
end
Run Code Online (Sandbox Code Playgroud)
现在我的问题是,当我进入控制台并写道:
ModuleOne::ModuleTwo::ClassOne.test
Run Code Online (Sandbox Code Playgroud)
它抛出以下内容: NameError: uninitialized constant ClassTwo
奇怪的是,问题似乎与使用class << self而不是相关self.method.如果我像这样更改class_one.rb文件就可以了!
module ModuleOne
module ModuleTwo
class ClassOne
def self.test
puts 'Class one'
ClassTwo.test
end
end
end
end
Run Code Online (Sandbox Code Playgroud)
我在application.rb中加载文件,如下所示:
config.autoload_paths += %W(#{config.root}/lib)
Run Code Online (Sandbox Code Playgroud)
这是rails中的一个错误,还是只是我弄错了?
我使用rails 3.1.3 btw
首先,恒定解析的基本过程是ruby首先搜索接收器的词法范围(包含引用的类或模块)ClassTwo,当它找不到它时,它会上升一个级别(Module.nesting返回此搜索路径) ) 等等.在你的情况下,这意味着寻找ModuleOne::ModuleTwo::ClassOne:ClassTwo,然后ModuleOne::ModuleTwo::ClassTwo,ModuleOne::ClassTwo等等.
如果失败,ruby会在封闭类/模块的继承层次结构中查找(例如,ClassOne的超类定义了一些东西.ancestors模块上的方法返回此搜索路径.最后,搜索顶层常量.
回到rails的神奇装载.这里有一个const_missing钩子被rails添加,当ruby找不到类时调用它,它基本上试图复制这个搜索逻辑,在每一步看到是否可以加载一个包含缺失常量的文件.
理想情况下,ruby会通过搜索路径(即嵌套)来搜索,但不幸的是它没有:当你引用时ClassTwo,const_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
Run Code Online (Sandbox Code Playgroud)
然后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
Run Code Online (Sandbox Code Playgroud)
因为现在调用const_missing的类有一个名称(ModuleOne :: ModuleTwo :: ClassOne :: FOO)rails'解决方法现在可以使用了.
我认为戴夫的解决方法是有效的,因为const_missing被调用ModuleOne::ModuleTwo::ClassOne而不是匿名的本征类/元类.
真正的解决办法是让红宝石通过const_missing筑巢.虽然已经打开了很长时间,但是有一个针对ruby记录的错误.所以是的,这可能被认为是魔法加载中的一个错误(还有其他边缘情况),但潜在的原因是ruby api的弱点迫使使用脆弱的变通方法.