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}"
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
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.)
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é".
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.