Ruby:使用class_eval定义常量只能通过const_get找到,而不能直接通过::lookup找到
给定一个 User 类:
class User
end
我想使用.class_eval. 所以:
User.class_eval { AVOCADO = 'fruit' }
User.class_eval { AVOCADO = 'fruit' }
-
如果我尝试通过 访问它
User::AVOCADO,我得到uninitialized constant User::AVOCADO,但User.const_get(:AVOCADO)有效。为什么? -
如果我在
included方法中的 Rails 关注中定义一个常量并将关注包含在User类中,我可以通过常规::查找访问它。例如:
module FruitConcern
extend ActiveSupport::Concern
included do
AVOCADO = 'fruit'
end
end
class User
include FruitConcern
end
User::AVOCADO
=> 'fruit'
但是,查找ActiveSupport::Concern的源代码,included只需将该块存储在实例变量 ( @_included_block) 中,然后它append_features就会覆盖will call base.class_eval(&@_included_block)。
那么,如果它只是User.class_eval使用同一个块调用,为什么User::AVOCADO在该included块内定义常量时有效,而当我User.class_eval { AVOCADO = 'fruit' }直接调用时却无效?
- 奇怪的是(请参阅此博客文章),在执行 时
User.class_eval { AVOCADO = 'teste' },Ruby 似乎也将常量泄漏到顶层。所以:
User.const_get(:AVOCADO)
=> "fruit"
AVOCADO
=> 'fruit'
我知道块具有平坦的范围,但是事先没有定义常量,我希望class_eval将self和都更改default definee为接收器。这里发生了什么?这个常量是如何在顶层和用户范围内被定义两次的?
回答
Ruby 中有三个隐式上下文:
self,“当前对象”:隐式接收器和实例变量的作用域。- 默认的 definee:在
def bar没有明确目标(即notdef foo.bar)的情况下最终定义的方法。 - 恒定范围。
前两个在链接的文章中得到了很好的解释。在链接的文章中承诺了第三篇文章,但它从未出现过。很多人写过很多关于常量的文字,可惜没有人写出权威的规范,类似于yugui对默认的definee所做的。
所以,不幸的是,我也只能推测,从而无益地增加了关于这个话题的大量非权威性词语。
块在 Ruby 中是词法作用域的,它们捕获它们的词法环境。通常,这意味着块内的引用与块外的含义完全相同,就好像块不存在一样。(当然块局部变量除外。)
所以,
fooAVOCADO = 'fruit'{ AVOCADO = 'fruit' }
意思是一样的
AVOCADO = 'fruit'除非,当然,
foo以某种方式改变了块被评估的上下文。我们知道instance_eval变化self。我们知道class_eval更改self和默认定义。然而,重要的是:class_eval不改变隐式常量作用域。因此,
AVOCADO = 'fruit'里面的赋值如果它在块之外,则具有完全相同的含义:
换句话说,它与顶层的常量赋值具有相同的含义,正如我们所知,在顶层:
self是未命名的单例对象,通常称为main,- 在默认definee是
Object,与添加的扭曲是方法成为private默认情况下,和 - 隐式常量范围也是
Object.
所以,AVOCADO在Object.
这意味着以下工作:
class User
AVOCADO
end
#=> 'fruit'
因为常量查找首先在词法上“向外”(在这种情况下它失败),然后在继承层次结构中“向上”,在那里它成功,因为User隐式是Object.
User.const_get(:AVOCADO)
#=> 'fruit'
也有效,因为这实际上与之前的相同,只是通过反射动态完成:它在 class 中启动常量查找算法User,就像你写的一样
class User
AVOCADO
end
但是,这不起作用:
User::AVOCADO
老实说,这是令人困惑的,因为AVOCADO 应该从Object.
回答
当您定义常量时,您并没有为self分配一个常量。您正在当前模块嵌套中定义一个常量。
当您显式打开一个类或模块时,您还设置了模块嵌套:
module Foo
BAR = 1
puts Module.nesting.inspect # [Foo]
end
当您执行User.class_eval { AVOCADO = 'fruit' }模块嵌套时,“主要”又名全局对象:
User.class_eval do
ADVOCADO = 'Fruit'
puts Module.nesting.inspect # []
end
块实际上不会改变模块嵌套。const_set另一方面在另一个模块嵌套中定义一个常量。
Ruby 实际上也没有两次定义常量。相反,当您使用 const_get 或引用一个常量而不明确使用范围解析运算符时,Ruby 将查看模块嵌套并将树遍历到全局范围:
class A
end
B = 'eureka'
A.const_get(:B) # 'eureka'
这就是如何引用顶级常量而不用::. 但是当您使用时,您User::ADVOCADO明确引用了User.
当涉及到令人担忧的示例时,您误解了正在发生的事情。完全不是关于class_eval. 您正在定义常量FruitConcern::AVOCADO。
然后,当您包含FruitConcern到User您正在添加FruitConcern到包含在常量查找中的祖先链中时:
module FruitConcern
ADVOCADO = 'fruit'
end
class User
include FruitConcern
end
User::ADVOCADO # fruit
看:
- Ruby 模块:包含 vs 前置 vs 扩展
- 你想知道的关于在 Ruby 中持续查找的一切