2010-11-06 3 views
57

Как динамически определить класс в 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 

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

Ответ: Метод

dynamic_name = "TestEval2" 

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

Я действительно не понимаю, чего вы хотите достичь. Существует класс TestEval2, после чего вы можете выполнить test_eval2 = TestEval2.new. И: class A ... end всегда дает nil, поэтому ваш выход в порядке, я думаю ;-) – Philip

+0

Это для теста TDD. Мне нужно динамически создать тестовый класс, а затем указать его имя, потому что он будет использоваться в дикой природе. sepp2K понял все правильно. – 2010-11-06 14:54:09

+2

@Philip: 'class A ... end' does * not * вычисляет значение' nil', он вычисляет значение последнего выражения, оцененного внутри него, как и любое другое составное выражение (блоки, методы, определения модулей, выражение групп) в Ruby. Так получилось, что во многих классах определения определения последнее выражение является выражением определения метода, которое оценивается как «nil». Но иногда полезно, чтобы тело определения класса оценивалось до определенного значения, например. в классе «я»; сам конец' идиома. –

ответ

106

полностью динамического решения с использованием sepp2k в имя класса просто имя первой константы, которая относится к нему.

I.e. если я сделаю 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 
+0

Отлично! Благодаря! Это именно то, что мне нужно. – 2010-11-06 14:52:55

+1

Спасибо. Это помогло мне здесь: https://github.com/validates-email-format-of/validates_email_format_of/blob/5ba707476162aefc66453df1a5abb1cb1f12eb1c/spec/validates_email_format_of_spec.rb#L11 –

+2

Ничего себе. Мне кажется очень странным, что (постоянное) присваивание имеет этот побочный эффект. –

27

Я тоже возился с этим. В моем случае я пытался проверить расширения на ActiveRecord :: Base. Мне нужно было динамически создавать класс, и поскольку активная запись просматривает таблицу на основе имени класса, этот класс не может быть анонимным.

Я не уверен, если это поможет вашему делу, но вот что я придумал:

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

    attr_accessible :foo, :bar 
end 

Насколько ActiveRecord обеспокоен, определение self.name было достаточно. Я предполагаю, что это будет работать во всех случаях, когда класс не может быть анонимным.

(. Я только что прочитал ответ sepp2k и я думаю, его лучше я оставлю это здесь в любом случае.)

+0

Кстати, вы МОЖЕТЕ просто задать имя таблицы для класса явно, например: 'self.table_name =" my_things "' –

0

Как aboutthe следующий код:

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 Безразлично»Retun объект класса времени выполнения, по крайней мере, на моем ПК это не так. Используйте Object.const_get для получения объекта Class.

+0

Это может быть выполнено без 'eval'. С помощью 'eval' вы должны дезинфицировать входные данные для хеджирования в отношении выполнения вредоносного кода. – Pistos

1

Я знаю, что это действительно старый вопрос, и некоторые другие рубисты могут отбросить меня из сообщества за это, но я работаю над созданием очень тонкой оболочки-обертки, которая обертывает популярный проект Java с рубиновыми классами. Основываясь на ответе @ sepp2k, я создал несколько вспомогательных методов, потому что мне приходилось делать это много раз в одном проекте. Обратите внимание, что I 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, «это вызвано извинениями: я сожалею».

Смежные вопросы