Динамічне визначення класу з назвою класу


74

Як я динамічно визначаю клас у 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) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"

1
Я насправді не отримую того, що ви хочете досягти. Існує клас TestEval2, ви можете зробити test_eval2 = TestEval2.new згодом. І: клас А ... кінець завжди дає нуль, тому, напевно, ваш результат нормальний ;-)
Філіп

Це для кроку тестування TDD. Мені потрібно динамічно створити тестовий клас, а потім вказати його назву, оскільки саме так він буде використовуватися в дикій природі. sepp2K все зрозуміло.

2
@Philip: class A ... endніяк НЕ обчислюватися nil, він оцінює на значення останнього обчисленого виразу всередині нього, так само як і будь-яке інше з'єднання вираження (блоки, методи, визначення модуля, які експресують групи) в Ruby. Так трапляється, що в багатьох тілах визначення класу останнім виразом є вираз визначення методу, який обчислює до nil. Але іноді корисно мати, щоб тіло визначення класу оцінювало до певного значення, наприклад, в class << self; self endідіомі.
Jörg W Mittag

Відповіді:


136

Назва класу - це просто назва першої константи, яка на нього посилається.

Тобто якщо я це зроблю 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
Дякую. Це мені допомогло тут: github.com/validates-email-format-of/validates_email_format_of/…
Ісаак Бетеш

3
Ого. Мені здається дуже дивним, що (постійне) призначення має цей побічний ефект.
Даніель Любаров

З якоїсь причини це працює для мене в розробці, але не у виробництві
sandre89

Я намагався встановити константу для самого нового класу, але безрезультатно. З Object це працює. Дякую.
Джон Дворжак

34

Я теж возився з цим. У моєму випадку я намагався протестувати розширення до 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
Ви, до речі, МОГЛИ просто встановити назву таблиці для класу явно, наприклад, так:self.table_name = "my_things"
Граф Дженкінс

@EarlJenkins Якщо в такому класі визначені асоціації (наприклад, належать_то), self.nameнеобхідно, інакше #demodulizeне вдасться на nil при спробі слідувати асоціації :( Принаймні, це сталося зі мною з Rails 5.2.3.
mlt

3

Я знаю, що це справді давнє запитання, і деякі інші рубісти можуть заважати мені від спільноти за це, але я працюю над створенням дуже тонкої самоцвіти-обгортки, яка обгортає популярний java-проект із класами рубіну. На основі відповіді @ sepp2k я створив кілька допоміжних методів, тому що мені доводилося робити це багато-багато разів в одному проекті. Зверніть увагу, що я розставив ці методи такими іменами, щоб вони не забруднювали якийсь простір імен верхнього рівня, як-от Об’єкт чи Ядро.

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 і дозволить спільноті з відкритим кодом, і я буду прикрашати ці класи в майбутньому, за необхідності. Це також створює більш дружній простір імен для використання класів. Оскільки мій самоцвіт є дуже-дуже тонкою обгорткою, мені довелося створити багато-багато підкласів та модулів для розширення інших модулів.

Як ми говоримо в JD Power, "це розвиток, зумовлений вибаченнями: вибачте".


1

Як щодо наступного коду:

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, щоб отримати об’єкт Class.


1
evalОднак це можна зробити і без цього . За допомогою evalви повинні очистити вхідні дані, щоб захиститися від шкідливого виконання коду.
Пістос,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.