具有类名的动态类定义

时间:2010-11-06 14:23:15

标签: ruby class dynamic metaprogramming

如何使用名称在Ruby中动态定义类?

我知道如何使用以下内容动态创建一个没有名称的类

dynamic_class = Class.new do
  def method1
  end
end

但是你不能指定一个类名。我想用名称动态创建一个类。

这是我想要做的一个例子,但当然它实际上并不起作用 (请注意,我不是创建类的实例而是创建类定义)

class TestEval
  def method1
    puts "name: #{self.name}"
  end
end

class_name = "TestEval"
dummy = eval("#{class_name}")

puts "dummy: #{dummy}"

dynamic_name = "TestEval2"
class_string = """
class #{dynamic_name}
  def method1
  end
end
"""
dummy2 = eval(class_string)
puts "dummy2: #{dummy2}" # doesn't work

实际输出:

dummy: TestEval
dummy2: 

期望的输出:

dummy: TestEval
dummy2: TestEval2

=============================================== =======

答案:使用sepp2k方法的完全动态解决方案

dynamic_name = "TestEval2"

Object.const_set(dynamic_name, Class.new)
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"

4 个答案:

答案 0 :(得分:123)

类的名称只是引用它的第一个常量的名称。

即。如果我myclass = Class.new然后MyClass = myclass,则该类的名称将变为MyClass。但是,如果我在运行时之前不知道类的名称,我就无法MyClass =

所以你可以使用Module#const_set来动态设置const的值。例如:

dynamic_name = "ClassName"
Object.const_set(dynamic_name, Class.new { def method1() 42 end })
ClassName.new.method1 #=> 42

答案 1 :(得分:31)

我也一直在搞乱这个。在我的情况下,我试图测试ActiveRecord :: Base的扩展。我需要能够动态创建一个类,并且因为活动记录根据类名查找一个表,所以该类不能是匿名的。

我不确定这是否有助于你的情况,但这就是我想出的:

test_model_class = Class.new(ActiveRecord::Base) do
  def self.name
    'TestModel'
  end

  attr_accessor :foo, :bar
end

就ActiveRecord而言,定义self.name就足够了。我猜这个实际上适用于所有不能匿名的类。

(我刚读过sepp2k的回答,我认为他的回答更好。无论如何,我会留在这里。)

答案 2 :(得分:2)

我知道这是一个非常古老的问题,而其他一些Rubyist可能会因此而避开社区,但我正在创建一个非常薄的包装宝石,用ruby类包装一个流行的java项目。基于@ sepp2k的答案,我创建了一些辅助方法,因为我必须在一个项目中多次执行此操作。请注意,我将这些方法命名为,以便它们不会污染像Object或Kernel这样的顶级命名空间。

module Redbeam
  # helper method to create thin class wrappers easily within the given namespace
  # 
  # @param  parent_klass [Class] parent class of the klasses
  # @param  klasses [Array[String, Class]] 2D array of [class, superclass]
  #   where each class is a String name of the class to create and superclass
  #   is the class the new class will inherit from
  def self.create_klasses(parent_klass, klasses)
    parent_klass.instance_eval do
      klasses.each do |klass, superklass|
        parent_klass.const_set klass, Class.new(superklass)
      end
    end
  end

  # helper method to create thin module wrappers easily within the given namespace
  # 
  # @param parent_klass [Class] parent class of the modules
  # @param modules [Array[String, Module]] 2D array of [module, supermodule]
  #   where each module is a String name of the module to create and supermodule
  #   is the module the new module will extend
  def self.create_modules(parent_klass, modules)
    parent_klass.instance_eval do
      modules.each do |new_module, supermodule|
        parent_klass.const_set new_module, Module.new { extend supermodule }
      end
    end
  end
end

使用这些方法(注意这是JRuby):

module Redbeam::Options
  Redbeam.create_klasses(self, [
    ['PipelineOptionsFactory', org.apache.beam.sdk.options.PipelineOptionsFactory]
  ])
  Redbeam.create_modules(self, [
    ['PipelineOptions', org.apache.beam.sdk.options.PipelineOptions]
  ])
end

<强> WHY ??

这允许我创建一个使用Java项目的JRuby gem,并允许开源社区和我在将来根据需要装饰这些类。它还创建了一个更友好的命名空间来使用这些类。由于我的gem是一个非常非常薄的包装器,我必须创建许多子类和模块来扩展其他模块。

正如我们在J.D.Power所说,“这是道歉驱动的发展:我很抱歉。”

答案 3 :(得分:0)

以下代码如何:

dynamic_name = "TestEval2"
class_string = """
class #{dynamic_name}
  def method1
  end
end
"""
eval(class_string)
dummy2 = Object.const_get(dynamic_name)
puts "dummy2: #{dummy2}"

Eval不会'返回运行时类对象,至少在我的PC上没有。使用Object.const_get获取Class对象。