Определение динамического класса с именем класса
Как динамически определить класс в 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}" # does not 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
Имя класса - это просто имя первой константы, которая ссылается на него.
т.е. если я сделаю myclass = Class.new
, а затем MyClass = myclass
, имя класса станет MyClass
. Однако я не могу сделать MyClass =
, если я не знаю имя класса до времени выполнения.
Поэтому вместо этого вы можете использовать Module#const_set
, который динамически устанавливает значение константы. Пример:
dynamic_name = "ClassName"
Object.const_set(dynamic_name, Class.new { def method1() 42 end })
ClassName.new.method1 #=> 42
Ответ 2
Я тоже возился с этим. В моем случае я пытался протестировать расширения ActiveRecord :: Base. Мне нужно было иметь возможность динамически создавать класс, и поскольку активная запись ищет таблицу на основе имени класса, этот класс не может быть анонимным.
Я не уверен, поможет ли это вашему делу, но вот что я придумал:
test_model_class = Class.new(ActiveRecord::Base) do
def self.name
'TestModel'
end
attr_accessor :foo, :bar
end
Что касается ActiveRecord, достаточно определить self.name. Я предполагаю, что это будет работать во всех случаях, когда класс не может быть анонимным.
(Я только что прочитал ответ sepp2k и думаю, что он лучше. Я все равно оставлю это здесь.)
Ответ 3
Я знаю, что это действительно старый вопрос, и некоторые другие рубисты могут отбросить меня из сообщества за это, но я работаю над созданием очень тонкой оболочки оболочки, которая обертывает популярный Java-проект с рубиновыми классами. Основываясь на ответе @sepp2k, я создал несколько вспомогательных методов, потому что мне приходилось делать это много раз в одном проекте. Обратите внимание, что я namespaced эти методы, чтобы они не загрязняли некоторые пространства верхнего уровня, такие как 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
ПОЧЕМУ??
Это позволяет мне создать жемчужину JRuby, которая использует проект Java, и позволит сообществу с открытым исходным кодом и я, в случае необходимости, украсить эти классы в будущем. Он также создает более дружественное пространство имен для использования классов. Поскольку мой драгоценный камень - очень тонкая оболочка, мне пришлось создать много, много подклассов и модулей для расширения других модулей.
Как мы говорим в J.D. Power, "это развитие, основанное на извинениях: извините".
Ответ 4
Как насчет следующего кода:
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 не перенастраивает объект класса времени выполнения, по крайней мере на моем ПК это не так. Используйте Object.const_get для получения объекта класса.