具有类名称的动态类定义


74

如何在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) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"

1
我真的没有得到您想要实现的目标。有一个类TestEval2,之后可以执行test_eval2 = TestEval2.new。并且:A类...结束总是产生nil,所以我想您的输出是可以的;-)
Philip 2010年

这是TDD测试步骤。我需要动态创建一个测试类,然后引用其名称,因为这是在野外使用的方式。sepp2K正确。

2
@Philip:class A ... end计算为nil,它计算到它里面最后计算的表达式的值,就像在红宝石每隔一个化合物式(块,方法,模块定义,表达式组)。碰巧的是,在许多类定义主体中,最后一个表达式是方法定义表达式,其结果为nil。但是有时让类定义主体评估为特定值(例如在class << self; self end习惯用法中)很有用。
约尔格W¯¯米塔格

Answers:


136

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

即如果我这样做myclass = Class.newMyClass = 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

优秀的!谢谢!那正是我所需要的。


3
哇。对我来说,(恒定)分配具有这种副作用似乎很奇怪。
Daniel Lubarov 2014年

由于某种原因,这对我来说对开发有效,但对我而言却
不可行

我试图将新类本身的常量设置为无效。与对象一起工作。谢谢。
John Dvorak

34

我也一直在搞这个。就我而言,我试图测试对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
顺便说一句,您可以只为该类显式设置表名,如下所示:self.table_name = "my_things"
Earl Jenkins

@EarlJenkins如果在此类中定义了关联(例如,belongs_to),self.name则有必要,否则在尝试遵循关联时#demodulize将失败,即为nil :(至少这是我在Rails 5.2.3中发生的事情
。– mlt

3

我知道这是一个非常老的问题,其他一些Ruby专家可能会因此而避开我,但是我正在创建一个非常薄的包装gem,用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

为什么??

这使我可以创建一个使用Java项目的JRuby gem,并允许开源社区和将来在必要时修饰这些类。它还创建了一个更友好的名称空间以使用这些类。由于我的gem是一个非常非常薄的包装器,因此我不得不创建很多子类和模块来扩展其他模块。

正如我们在JD Power所说的那样,“这是由道歉驱动的开发:很抱歉”。


1

下面的代码怎么样:

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不会重新调整运行时Class对象,至少在我的PC上不会。使用Object.const_get获取Class对象。


1
但是,无需即可完成eval。使用eval,您必须清理输入以对冲恶意代码执行。
皮斯托斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.