web-dev-qa-db-fra.com

Définition de classe dynamique AVEC un nom de classe

Comment puis-je définir dynamiquement une classe dans Ruby AVEC un nom?

Je sais comment créer une classe dynamiquement sans un nom en utilisant quelque chose comme:

dynamic_class = Class.new do
  def method1
  end
end

Mais vous ne pouvez pas spécifier de nom de classe. Je veux créer une classe dynamiquement avec un nom.

Voici un exemple de ce que je veux faire, mais bien sûr, cela ne fonctionne pas.
(Notez que je ne crée pas une instance d'une classe mais une définition de classe)

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

Sortie réelle:

dummy: TestEval
dummy2: 

Sortie désirée:

dummy: TestEval
dummy2: TestEval2

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

Réponse: Une solution totalement dynamique utilisant la méthode de sepp2k

dynamic_name = "TestEval2"

Object.const_set(dynamic_name, Class.new) # If inheriting, use Class.new( superclass )
dummy2 = eval("#{dynamic_name}")
puts "dummy2: #{dummy2}"
67
Mike Bethany

Le nom d'une classe est simplement le nom de la première constante qui s'y réfère.

C'est à dire. si je fais myclass = Class.new puis MyClass = myclass, le nom de la classe deviendra MyClass. Mais je ne peux pas faire MyClass = si je ne connais pas le nom de la classe avant l'exécution.

Vous pouvez donc utiliser Module#const_set, qui définit dynamiquement la valeur d'une constante. Exemple:

dynamic_name = "ClassName"
Object.const_set(dynamic_name, Class.new { def method1() 42 end })
ClassName.new.method1 #=> 42
124
sepp2k

J'ai aussi joué avec ça. Dans mon cas, j'essayais de tester les extensions d'ActiveRecord :: Base. J'avais besoin de pouvoir créer dynamiquement une classe, et parce que l'enregistrement actif recherche une table basée sur un nom de classe, cette classe ne pouvait pas être anonyme.

Je ne sais pas si cela aide votre cas, mais voici ce que j'ai trouvé:

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

  attr_accessor :foo, :bar
end

En ce qui concerne ActiveRecord, la définition de self.name était suffisante. Je suppose que cela fonctionnera réellement dans tous les cas où une classe ne peut pas être anonyme.

(Je viens de lire la réponse de sepp2k et je pense que la sienne est meilleure. Je vais laisser cela de toute façon.)

31
Will Tomlins

Je sais que c'est une très vieille question, et certains autres rubyistes pourraient me fuir la communauté pour cela, mais je travaille sur la création d'une gemme de wrapper très mince qui enveloppe un populaire Java avec = Ruby classes. Sur la base de la réponse de @ sepp2k, j'ai créé quelques méthodes d'assistance parce que je devais le faire plusieurs fois dans un même projet. Notez que j'ai nommé ces méthodes afin qu'elles ne polluent pas certaines espace de noms de niveau supérieur comme Object ou 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

Pour utiliser ces méthodes (notez qu'il s'agit de 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

POURQUOI ??

Cela me permet de créer un joyau JRuby qui utilise le projet Java et permettrait à la communauté open source et à moi de décorer ces classes à l'avenir, si nécessaire. Cela crée également un espace de noms plus convivial pour utilisez les classes dans. Comme ma gemme est un wrapper très, très mince, j'ai dû créer beaucoup, beaucoup de sous-classes et de modules pour étendre d'autres modules.

Comme nous le disons chez J.D. Power, "il s'agit d'un développement axé sur les excuses: je suis désolé".

3
WattsInABox

Que diriez-vous du code suivant:

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 ne renvoie pas l'objet d'exécution de classe, du moins sur mon PC, il ne le fait pas. Utilisez Object.const_get pour obtenir l'objet Class.

0
wuyunxia