Определение динамического класса с именем класса - PullRequest
66 голосов
/ 06 ноября 2010

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

Ответы [ 4 ]

123 голосов
/ 06 ноября 2010

Имя класса - это просто имя первой константы, которая ссылается на него.

т.е. если я сделаю 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
31 голосов
/ 31 октября 2012

Я тоже возился с этим. В моем случае я пытался протестировать расширения 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 голосов
/ 20 мая 2017

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

ПОЧЕМУ ??

Это позволяет мне создать гем JRuby, который использует проект Java, и позволит сообществу открытого исходного кода и мне в будущем при необходимости украшать эти классы. Это также создает более дружественное пространство имен для использования классов. Так как мой гем - очень, очень тонкая оболочка, мне пришлось создать много-много подклассов и модулей для расширения других модулей.

Как мы говорим в J.D. Power, «это разработка, основанная на извинениях: извините».

0 голосов
/ 17 декабря 2014

Как насчет следующего кода:

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

...