ruby fonction - Définition de classe dynamique AVEC un nom de classe





variable instance (5)


Je sais que c'est une question vraiment ancienne, et d'autres Rubyistes pourraient m'éviter de la communauté pour cela, mais je travaille sur la création d'une gemme d'emballage très mince qui enveloppe un projet java populaire avec des classes ruby. Basé sur la réponse de @ sepp2k, j'ai créé des méthodes d'aide en couple parce que je devais le faire beaucoup, plusieurs fois dans un projet. Notez que j'ai nommé ces méthodes afin qu'elles ne polluent pas certains espaces 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 que ceci est 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 une gemme JRuby qui utilise le projet Java et permettrait à la communauté open source et moi de décorer ces classes dans le futur, si nécessaire. Il crée également un espace de noms plus convivial pour utiliser les classes. Puisque mon gem 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 JD Power, "c'est un développement axé sur les excuses: je suis désolé".

Comment définir dynamiquement une classe dans Ruby avec un nom?

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

dynamic_class = Class.new do
  def method1
  end
end

Mais vous ne pouvez pas spécifier un 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 vraiment.
(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)
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 . Cependant, je ne peux pas faire MyClass = si je ne connais pas le nom de la classe avant l'exécution.

Donc à la place, vous pouvez utiliser le Module#const_set , qui définit dynamiquement la valeur d'un const. 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 à 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 peut pas être anonyme.

Je ne suis pas sûr que 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_accessible :foo, :bar
end

En ce qui concerne ActiveRecord, définir self.name était suffisant. Je suppose que cela fonctionnera dans tous les cas où une classe ne peut pas être anonyme.

(Je viens juste de lire la réponse de sepp2k et je pense que c'est mieux, je vais laisser ça de toute façon.)




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 Class d'exécution, du moins sur mon PC. Utilisez Object.const_get pour obtenir l'objet Class.




Une méthode singleton est une méthode définie uniquement pour un seul objet.

Exemple:

class SomeClass
  class << self
    def test
    end
  end
end

test_obj = SomeClass.new

def test_obj.test_2
end

class << test_obj
  def test_3
  end
end

puts "Singleton's methods of SomeClass"
puts SomeClass.singleton_methods
puts '------------------------------------------'
puts "Singleton's methods of test_obj"
puts test_obj.singleton_methods

Les méthodes de Singleton de Singleton

tester

Les méthodes de Singleton de test_obj

test_2

test_3





ruby class dynamic metaprogramming