什么时候需要在 Ruby 常量前添加“::”前缀? (未初始化的常量错误)

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

错误类似于

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-on-rails ruby inheritance constants autoload
2个回答
1
投票

什么时候需要在 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


0
投票

绕过正常查找以使用最顶层常量

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

这并没有真正改变您的具体示例,或者在较高层面上改变使用 
::

运算符来消除常量歧义的实用价值。请注意,这两项的范围略有不同,这可能会影响代码的其他方面。

    

© www.soinside.com 2019 - 2024. All rights reserved.