首页 > 解决方案 > Ruby:使用 class_eval 定义常量只能通过 const_get 找到,但不能直接通过 :: 查找

问题描述

给定一个用户类:

class User
end

我想使用.class_eval. 所以:

User.class_eval { AVOCADO = 'fruit' }
  1. 如果我尝试通过 访问它User::AVOCADO,我会得到uninitialized constant User::AVOCADO,但User.const_get(:AVOCADO)可以。为什么?

  2. 如果我在included方法的 Rails Concern 中定义了一个常量并将该关注点包含在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_featureswill call base.class_eval(&@_included_block)

所以,如果它只是User.class_eval用同一个块调用,为什么User::AVOCADO在该块内定义常量时起作用included,但当我User.class_eval { AVOCADO = 'fruit' }直接调用时不起作用?

  1. 奇怪的是(见这篇博文),当做 时User.class_eval { AVOCADO = 'teste' },似乎 Ruby 也将常量泄漏到顶层。所以:
User.const_get(:AVOCADO)
=> "fruit"
AVOCADO
=> 'fruit'

我知道块有平面范围,但没有事先定义常量,我希望class_eval同时改变接收器selfdefault definee接收器。这里发生了什么?这个常量是如何在顶层和用户范围内定义两次的?

标签: ruby-on-railsruby

解决方案


当您定义常量时,您不会将常量分配给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到包含在常量查找中的祖先链中:UserFruitConcern

module FruitConcern
  ADVOCADO = 'fruit'
end

class User
  include FruitConcern
end

User::ADVOCADO  # fruit

看:


推荐阅读