如何在匿名类定义中命名空间常量的名称空间?

时间:2018-08-22 10:16:21

标签: ruby

通过Class.new创建匿名类时,它们似乎没有自己的常量名称空间:

klass1 = Class.new do
  FOO = "foo"
end

klass2 = Class.new do
  FOO = "bar"
end

这给出了warning: already initialized constant FOO,看起来是正确的:

> klass1.const_get(:FOO)
"bar"
> klass2.const_get(:FOO)
"bar"
> FOO
"bar"

我将在简单的DSL中使用这种方法来定义应用程序的插件,如下所示:

class App
  class AddonBase

    attr_reader :session

    def initialize(session)
      @session = session
    end
  end

  def self.addons
    @addons ||= {}
  end

  def self.addon(name, &block)
    addons[name] = Class.new(AddonBase, &block)
  end
end

这对于简单的加载项很好,但是如果定义常量,它们将位于Object::下,而不是变成addons[name]::CONSTANT

App.addon "addon1" do

  PATH="/var/run/foo"

  def execute
    File.touch(PATH)
  end
end

App.addon "addon2" do

  PATH="/etc/app/config"

  def execute
    File.unlink(PATH)
  end
end

# warning: already initialized constant PATH

常量可以是任何东西,附加组件甚至可以定义自己的实用程序子类,因此,这不仅仅是将PATH替换为函数。

有什么办法可以解决这个问题?

3 个答案:

答案 0 :(得分:2)

  

通过Class.new创建匿名类时,它们似乎没有用于常量的命名空间

当然,根据“匿名”一词的定义。比较以下两个摘要:

class C1; puts "|#{name}|"; end
#⇒ |C1|
C2 = Class.new { puts "|#{name}|" }
#⇒ ||

除非分配给常量,否则该类没有名称,因此内部定义的所有常量都转到Object命名空间。也就是说,这里的警告实际上是指出错误,并且Object::FOO = "bar"会覆盖Object::FOO = "foo"常量。

也就是说,在这种情况下不能使用常量。改为使用类级实例变量,或手动构造唯一的常量名称(尽管如此,我建议避免使用一堆无关的常量来污染Object类。)

答案 1 :(得分:2)

  

通过Class.new创建匿名类时,它们似乎没有自己的常量名称空间

可以,您可以使用const_set在匿名类中定义常量:

klass1 = Class.new do
  const_set(:FOO, 'foo')
end

klass2 = Class.new do
  const_set(:FOO, 'bar')
end

klass1::FOO #=> "foo"
klass2::FOO #=> "bar"

或通过self::

klass1 = Class.new do
  self::FOO = 'foo'
end

klass2 = Class.new do
  self::FOO = 'bar'
end

klass1::FOO #=> "foo"
klass2::FOO #=> "bar"

答案 2 :(得分:1)

实际上,问题是如何使用包含常量定义的proc来定义类。如前所述,这是不可能的,因为proc获得class_eval'd且不允许定义常量。

我建议另一种方法。您可以使用模块代替proc来定义将模块混合到类中的新插件吗?

示例:

module AddonModule
  FOO = "foo"
end

klass = Class.new
klass.include AddonModule

klass::FOO # => "foo"

在您的DSL中的使用:

def self.addon(name, addon_module)
  addon = Class.new(AddonBase)
  addon.include addon_module

  addons[name] = addon
end