如何使用名称在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}"
答案 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对象。