错误类似于
NameError: uninitialized constant Foo::Bar
(当 Bar
不是 Foo
的后代时)。
我知道它与加载(自动加载?)常量有关,并且我感觉
lib/
内部的任何内容都可以安全地不以::
为前缀,因为它是自动加载的(或其他东西)。
我刚刚发生的一个例子是这样的:
应用程序/资产/类/base_class.rb
class BaseClass
end
应用程序/资产/some_module/some_class.rb
module SomeModule
class SomeClass < BaseClass
end
end
我正在运行规范并收到“加载[文件]时发生错误”:
NameError: uninitialized constant SomeModule::SomeClass::BaseClass
。
现在,我知道它正在尝试在
BaseClass
内寻找 SomeModule::SomeClass
。但这在几个小时前还在工作,然后在对这些文件没有任何更改后停止了。
所以,我可以加上
::
并使用 class SomeClass < ::BaseClass
,但不明白为什么感觉不好,然后我想,我是否需要一直在所有代码中添加 ::
?
什么时候需要在 Ruby 常量前添加“::”前缀?
当你引用一个常量时,Ruby 会在当前模块嵌套中查找它。
module SomeModule
puts Module.nesting.inspect # the current module nesting is [SomeModule]
end
模块嵌套主要通过使用
module
和 class
关键字来打开类/模块来设置。
如果在那里没有找到它,它将在模块嵌套中继续向上,直到到达 main,如果到那时仍然没有找到常量,那么您将得到一个丢失常量的错误。
此错误可能会有些令人困惑,因为该消息包含开始查找常量的模块嵌套。
通过在常量前面添加
::
,您明确告诉 Ruby 从顶级命名空间解析该常量:
module Bar
def self.hello
puts "Hello from the top level Bar"
end
end
module Foo
module Bar
def self.hello
puts "Hello from Foo::Bar"
end
end
def self.test
Bar.hello # the current module nesting is Foo.
::Bar.hello
end
end
Foo.test
# Will output:
# Hello from Foo::Bar
# Hello from the top level Bar
在大多数情况下,这并不是严格必要的,但显式引用自己的命名空间(依赖项)之外的常量是一个很好的做法。
那么为什么我的代码停止工作了?
如果没有实际的可复制示例,几乎不可能分辨。它可能是“与程序员相关的问题”(您搞砸了并移动/删除/重命名了文件),也可能与使用中的自动加载器的问题有关。由于猴子补丁的方式,经典的自动加载器更容易出现错误行为
Object#constant_missing
。
Ruby 有一个基于范围和命名空间的预定义的各种事物的查找顺序。当你使用
::ModuleName
时,你实际上是在告诉 Ruby 避免正常的查找行为,并在它认为最顶层的命名空间(通常是最顶层的祖先)中找到以 ::
为前缀的模块或类对象的 constant或(尤其是在 irb 内)TopLevel 中的常量。
换句话说,如果您想务实地考虑它而不是担心解释器在幕后做什么,
::Foo::Bar
会绕过正常的查找行为,类似于以下代码片段。
which_foo = Foo.ancestors.pop
which_bar = which_foo.const_get :Bar
为了避免自行车脱落,请注意,这不是严格正确的,因为当您调用
::Foo
时,查找实际上是在最顶层范围开始的,但我认为它足以说明这个概念来回答您的问题。
当您想要确保您没有调用精炼对象时,或者 Ruby 的正常查找会在当前范围内找到具有不正确行为的不同
Foo
时,这种前缀技术可能很重要。一些 DSL(例如 Puppet)依靠这种技术来避免命名空间冲突,并将其视为最佳实践,但在实际情况下,它主要是一种安全带和吊带方法,用于库/gems 中的安全,而不是在您通常“应该”的应用程序中了解潜在冲突常量的范围,或者可以通过合理的命名约定完全避免该问题。
嵌套常量与命名空间常量的注意事项
# nested modules
module Foo
Module Bar
end
end
# namespaced module
module Foo::Bar
这并没有真正改变您的具体示例,或者在较高层面上改变使用
::
运算符来消除常量歧义的实用价值。请注意,这两项的范围略有不同,这可能会影响代码的其他方面。