2010-11-06 14 views
57

¿Cómo se define dinámicamente una clase en Ruby WITH a name?Definición de clase dinámica CON nombre de clase

sé cómo crear una clase dinámica sin un nombre usando algo como:

dynamic_class = Class.new do 
    def method1 
    end 
end 

pero no se puede especificar un nombre de clase. Quiero crear una clase dinámicamente con un nombre.

Aquí hay un ejemplo de lo que quiero hacer pero, por supuesto, en realidad no funciona.
(Nótese que no estoy creando una instancia de una clase, sino una definición de clase)

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 

salida real:

dummy: TestEval 
dummy2: 

salida deseada:

dummy: TestEval 
dummy2: TestEval2 

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

Respuesta: método

dynamic_name = "TestEval2" 

Object.const_set(dynamic_name, Class.new) 
dummy2 = eval("#{dynamic_name}") 
puts "dummy2: #{dummy2}" 
+1

Realmente no consigo lo que quiere lograr. Hay una clase TestEval2, puedes hacer test_eval2 = TestEval2.new después. Y: clase A ... final siempre produce nulo, por lo que su salida está bien, supongo ;-) – Philip

+0

Es para un paso de prueba TDD. Necesito crear una clase de prueba dinámicamente y luego hacer referencia a su nombre porque así es como se usará en la naturaleza. sepp2K lo hizo bien. –

+2

@Philip: 'clase A ... fin' no * evalúa como' nil', evalúa el valor de la última expresión evaluada dentro de él, como cualquier otra expresión compuesta (bloques, métodos, definiciones de módulos, expresión grupos) en Ruby. Sucede que en muchos cuerpos de definición de clase, la última expresión es una expresión de definición de método, que se evalúa como 'nil'. Pero a veces es útil tener un cuerpo de definición de clase evaluado a un valor específico, p. en el 'clase << uno mismo; lenguaje autofinal. –

Respuesta

106

una solución totalmente dinámico utilizando de sepp2k El nombre de una clase es simplemente el nombre de la primera constante que se refiere a ella.

I.e. si hago myclass = Class.new y luego MyClass = myclass, el nombre de la clase se convertirá en MyClass. Sin embargo, no puedo hacer MyClass = si no sé el nombre de la clase hasta el tiempo de ejecución.

Por lo tanto, puede usar Module#const_set, que establece dinámicamente el valor de una const. Ejemplo:

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

¡Excelente! ¡Gracias! Eso es exactamente lo que necesitaba. –

+1

Gracias. Esto me ayudó aquí: https://github.com/validates-email-format-of/validates_email_format_of/blob/5ba707476162aefc66453df1a5abb1cb1f12eb1c/spec/validates_email_format_of_spec.rb#L11 –

+2

Wow. Me parece muy extraño que la asignación (constante) tenga este efecto secundario. –

27

He estado jugando con esto también. En mi caso, estaba intentando probar las extensiones de ActiveRecord :: Base. Necesitaba poder crear dinámicamente una clase, y como el registro activo busca una tabla basada en un nombre de clase, esa clase no puede ser anónima.

no estoy seguro de si esto ayuda a su caso, pero esto es lo que ocurrió:

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

    attr_accessible :foo, :bar 
end 

En lo que se refiere a ActiveRecord, self.name definir era suficiente. Supongo que esto funcionará en todos los casos donde una clase no puede ser anónima.

(. Acabo de leer la respuesta de sepp2k y estoy pensando que la suya es mejor que voy a dejar esto aquí de todos modos.)

+0

Incidentalmente, PODRÍA simplemente establecer el nombre de la tabla para la clase explícitamente, así: 'self.table_name =" my_things "' –

0

Cómo aboutthe siguiente código:

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 doesn' retun el objeto Class de tiempo de ejecución, al menos en mi PC no lo hace. Use Object.const_get para obtener el objeto Class.

+0

Sin embargo, se puede lograr sin 'eval'. Con 'eval', debe desinfectar la entrada para protegerse contra la ejecución de código malicioso. – Pistos

1

Sé que esta es una pregunta muy antigua, y algunos otros Rubyists podrían rechazarme de la comunidad por esto, pero estoy trabajando en la creación de una gema envoltura muy delgada que envuelve un popular proyecto de Java con clases de ruby. Basado en la respuesta de @sepp2k, creé un par de métodos auxiliares porque tuve que hacer esto muchas, muchas veces en un proyecto. Tenga en cuenta que el espacio de nombres de estos métodos para que no estén contaminando un espacio de nombres de alto nivel como Object o 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 

Para utilizar estos métodos (tenga en cuenta que este es 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 

QUÉ ??

Esto me permite crear una gema JRuby que utiliza el proyecto Java y permitiría a la comunidad de código abierto y a decorar estas clases en el futuro, según sea necesario. También crea un espacio de nombres más amigable para usar las clases. Como mi gema es una envoltura muy, muy delgada, tuve que crear muchas, muchas subclases y módulos para extender otros módulos.

Como decimos en J.D. Power, "este es un desarrollo impulsado por la apología: lo siento".