Подтвердить что ты не робот

Определение динамического класса с именем класса

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

Ответ 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 для получения объекта класса.