在下面的示例中,puts Post::User.foo
行打印foo
。换句话说,Post::User
会返回一个全局User
常量。
class User
def self.foo
"foo"
end
end
class Post
puts Post::User.foo
end
# => warning: toplevel constant User referenced by Post::User
# => foo
第二个例子引发了一个错误,因为没有找到常量。
module User
def self.foo
"foo"
end
end
module Post
puts Post::User.foo
end
# => uninitialized constant Post::User (NameError)
方案#2的结果更直观。为什么在场景#1中找到常量?如果在场景#1中返回User
常量,为什么场景#2中不会发生这种情况?在场景#2中,Post
是一个模块,所以在这种情况下,应该在Object.ancestors
中搜索常量,这也应该返回User
常量,但这不会发生。
答案 0 :(得分:9)
行为是由模块内部而不是类中的查找引起的。 (你在模块中查找模块并且类中的类是无关紧要的事实。)
module M
def self.foo; "moo" end
end
class C
def self.foo; "coo" end
end
class A
A::M.foo rescue puts $! # warning: toplevel constant M referenced by A::M
A::C.foo rescue puts $! # warning: toplevel constant M referenced by A::M
end
module B
B::M.foo rescue puts $! # error: uninitialized constant B::M
B::C.foo rescue puts $! # error: uninitialized constant B::C
end
如果查看C代码,这两个代码都会调用rb_const_get_0,其中exclude = true,recurse = true,visibility = true。
在A :: M的情况下,这会查找:
tmp = A
tmp::M # (doesn't exist)
tmp = tmp.super # (tmp = Object)
tmp::M # (exists, but warns).
在B :: M的情况下,这会查找:
tmp = B
tmp::M # (doesn't exist)
tmp = tmp.super # (modules have no super so lookup stops here)
因为exclude为true,所以跳过跳转到(tmp = Object
)的模块的通常边缘情况。这意味着B::M
与module B; M; end
的行为不同,这可以说是红宝石中的不一致。
答案 1 :(得分:7)
首先,请考虑所有顶级常量都在类Object
中定义,因为Ruby是一种面向对象的语言,并且不能有变量或常量不属于某个类:
class A; end
module B; end
A == Object::A # => true
B == Object::B # => true
其次,类Object
默认为任何类的祖先,但不包含模块:
class A; puts ancestors; end # => [A, Object, Kernel, BasicObject]
module B; puts ancestors; end # => []
与此同时,Object
中都没有Module.nesting
:
class A; puts Module.nesting; end # => [A]
module B; puts Module.nesting; end # => [B]
然后,the Matz book mentioned above的第7.9章说Ruby在Module.nesting
然后在ancestors
中搜索任何常量。
因此,在您的示例中,它为类User
找到常量Post
,因为Ruby在类{{1}中定义顶级常量User
(Object
)。 Object::User
是Object
的祖先:
Post
但您的模块Object::User == Post::User # => true
的{{1}}或Object
中没有ancestors
。常量Module.nesting
在类Post
中定义为Post
,但它不是来自Object
,因为Object::Post
不是对象 。因此,它无法通过Object
解析模块 module Post
中的常量User
。
同时,如果您将Post
保持为ancestors
并将User
转变为某个类,它将会有效:
module
这是因为Post
可以解决其内部的任何常量超级module User
def self.foo
"foo"
end
end
class Post
puts Post::User.foo
end
# => foo
,所有顶级常量都在{{1}中定义}。
答案 2 :(得分:0)
ruby中的**
class
关键字实际上是一个接受Constant并为其定义类的方法名称
如果是方案1,则调用puts Post::User.foo
时。 Ruby查看它是否定义了类Post::User
(Ruby将其作为常量进行搜索,因为它就是这样)。一旦找到它,就会调用foo方法。
但是在方案2中,您已经在模块中定义了它,因为调用puts Post::User.foo
并且不存在Post::User
这样的类。搜索失败,您会收到明显的错误消息。
您可以在this链接中引用类名称为常量部分以获取更多详细信息。
答案 3 :(得分:0)
我认为问题在于Class和Module(ascii image)的继承级别。实际上,Class对象是从Module对象继承的。查找的祖先。
module A; end
p A.ancestors #=> [A]
class B; end
p B.ancestors #=> [B, Object, Kernel, BasicObject]
这意味着如果module A
中的查找算法步骤,它就无法退出。
因此module Post
的{{1}} Post::User
查找算法类似于
Post
(找到module Post
)User
中找到const module Post
(找不到,去找Post
的祖先)以及class Post
Post
(找到class Post
)User
中找到const class Post
(找不到,去找Post
的祖先)User
(找到class User
并发出警告)这就是为什么你可以在一个命名空间级别链接类,而ruby仍然可以找到它们。如果你试试
class User
def self.foo
"foo"
end
end
class A1; end
class A2; end
class Foo
p Foo::A1::A2::User.foo
end
#... many of warnings
#> "foo"
仍然很好。 和
class User
def self.foo
"foo1"
end
end
class A1; end
module A2; end
class Foo
p Foo::A1::A2::User.foo
end
#>.. some warnings
#> uninitialized constant A2::User (NameError)
因为查找算法在模块中执行并被困。
<强> TL; DR 强>
class User
def self.foo
"foo"
end
end
class Post1
Post1.tap{|s| p s.ancestors}::User.foo #=> [Post1, Object, Kernel, BasicObject]
end
# ok
module Post2
Post2.tap{|s| p s.ancestors}::User.foo #=> [Post2]
end
# error
<强>更新强>
在这种情况下,调用Post :: User.foo的地方并没有起到太大作用。它也可以在类/模块之外,行为也是一样的。
module ModuleA; end
class ClassB; end
class ClassC; end
class E; ModuleA::ClassC; end # error
module F; ClassB::ClassC; end # ok
ClassB::ClassC # ok
ClassB::ModuleA # ok
ModuleA::ClassB # error
正如你指出的那样
当然,模块或类的祖先不同,但在这种情况下 模块,Object.ancestors中的附加常量查找发生。
在&#34; initial&#34;时刻仅抬头。这意味着在
的情况下module Post; Post::User.foo; end
首先会查看 const Post
(这里我可能会误解某些内容)Post.ancestors
,因为没有Post::Post
,继续在Object.ancestors
中查找(然后它会请使用我在上面描述的算法。
总结,你所谓的const的上下文只对第一个(最左边)const查找有用。然后只剩下考虑的对象。
A::B::C
A # consider context
::B # consider only A
::C # consider only B