Agregando una variable de instancia a una clase en Ruby

¿Cómo puedo agregar una variable de instancia a una clase definida en tiempo de ejecución , y luego obtener y establecer su valor desde fuera de la clase?

Estoy buscando una solución de metaprogtwigción que me permita modificar la instancia de la clase en tiempo de ejecución en lugar de modificar el código fuente que originalmente definió la clase. Algunas de las soluciones explican cómo declarar variables de instancia en las definiciones de clase, pero eso no es lo que estoy preguntando.

Puedes usar los atributos de acceso:

class Array attr_accessor :var end 

Ahora puedes acceder a través de:

 array = [] array.var = 123 puts array.var 

Tenga en cuenta que también puede usar attr_reader o attr_writer para definir solo getters o setters o puede definirlos manualmente como tales:

 class Array attr_reader :getter_only_method attr_writer :setter_only_method # Manual definitions equivalent to using attr_reader/writer/accessor def var @var end def var=(value) @var = value end end 

También puede usar métodos singleton si solo quiere que esté definido en una sola instancia:

 array = [] def array.var @var end def array.var=(value) @var = value end array.var = 123 puts array.var 

Para su información, en respuesta al comentario sobre esta respuesta, el método singleton funciona bien, y lo siguiente es una prueba:

 irb(main):001:0> class A irb(main):002:1> attr_accessor :b irb(main):003:1> end => nil irb(main):004:0> a = A.new => # irb(main):005:0> ab = 1 => 1 irb(main):006:0> ab => 1 irb(main):007:0> def a.setit=(value) irb(main):008:1> @b = value irb(main):009:1> end => nil irb(main):010:0> a.setit = 2 => 2 irb(main):011:0> ab => 2 irb(main):012:0> 

Como puede ver, el método singleton setit establecerá el mismo campo, @b , como el definido utilizando attr_accessor … por lo que un método singleton es un enfoque perfectamente válido para esta pregunta.

Ruby proporciona métodos para esto, instance_variable_get y instance_variable_set . ( docs )

Puedes crear y asignar nuevas variables de instancia como esta:

 >> foo = Object.new => # >> foo.instance_variable_set(:@bar, "baz") => "baz" >> foo.inspect => # 

@ Readonly

Si su uso de “clase MyObject” es un uso de una clase abierta, tenga en cuenta que está redefiniendo el método de inicialización.

En Ruby, no existe tal cosa como sobrecargar … solo anular o redefinir … en otras palabras, solo puede haber una instancia de cualquier método dado, así que si lo redefinimos, se redefinirá … y se inicializará. El método no es diferente (aunque es lo que utiliza el nuevo método de objetos de clase).

Por lo tanto, nunca vuelva a definir un método existente sin crear un alias primero … al menos si desea acceder a la definición original. Y redefinir el método de inicialización de una clase desconocida puede ser bastante arriesgado.

En cualquier caso, creo que tengo una solución mucho más simple para usted, que utiliza la metaclase real para definir los métodos singleton:

 m = MyObject.new metaclass = class << m; self; end metaclass.send :attr_accessor, :first, :second m.first = "first" m.second = "second" puts m.first, m.second 

Puedes usar las clases abiertas y de metaclase para hacerte aún más complicado y hacer algo como:

 class MyObject def metaclass class << self self end end def define_attributes(hash) hash.each_pair { |key, value| metaclass.send :attr_accessor, key send "#{key}=".to_sym, value } end end m = MyObject.new m.define_attributes({ :first => "first", :second => "second" }) 

Lo anterior básicamente es exponer la metaclase a través del método "metaclase", luego usarla en define_attributes para definir dinámicamente un grupo de atributos con attr_accessor, y luego invocar al definidor de atributos con el valor asociado en el hash.

Con Ruby puedes ser creativo y hacer lo mismo de muchas maneras diferentes 😉


Para tu información, en caso de que no lo supieras, usar la metaclase como lo he hecho significa que solo estás actuando en la instancia dada del objeto. Por lo tanto, invocar define_attributes solo definirá esos atributos para esa instancia en particular.

Ejemplo:

 m1 = MyObject.new m2 = MyObject.new m1.define_attributes({:a => 123, :b => 321}) m2.define_attributes({:c => "abc", :d => "zxy"}) puts m1.a, m1.b, m2.c, m2.d # this will work m1.c = 5 # this will fail because c= is not defined on m1! m2.a = 5 # this will fail because a= is not defined on m2! 

La respuesta de Mike Stone ya es bastante completa, pero me gustaría agregar algunos detalles.

Puede modificar su clase en cualquier momento, incluso después de que se haya creado alguna instancia, y obtener los resultados que desea. Puedes probarlo en tu consola:

 s1 = 'string 1' s2 = 'string 2' class String attr_accessor :my_var end s1.my_var = 'comment #1' s2.my_var = 'comment 2' puts s1.my_var, s2.my_var 

Las otras soluciones funcionarán perfectamente también, pero aquí hay un ejemplo que utiliza define_method, si estás empeñado en no usar clases abiertas … definirá la variable “var” para la clase de matriz … pero ten en cuenta que es EQUIVALENTE para usar una clase abierta … el beneficio es que puede hacerlo para una clase desconocida (por lo que cualquier clase de objeto, en lugar de abrir una clase específica) … también define_method funcionará dentro de un método, mientras que no puede abrir una clase dentro de un método.

 array = [] array.class.send(:define_method, :var) { @var } array.class.send(:define_method, :var=) { |value| @var = value } 

Y aquí hay un ejemplo de su uso … tenga en cuenta que array2, una matriz DIFERENTE también tiene los métodos, por lo que si esto no es lo que quiere, probablemente desee los métodos singleton que expliqué en otra publicación.

 irb(main):001:0> array = [] => [] irb(main):002:0> array.class.send(:define_method, :var) { @var } => # irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value } => # irb(main):004:0> array.var = 123 => 123 irb(main):005:0> array.var => 123 irb(main):006:0> array2 = [] => [] irb(main):007:0> array2.var = 321 => 321 irb(main):008:0> array2.var => 321 irb(main):009:0> array.var => 123 

Solo lectura, en respuesta a su edición:

Edición: Parece que necesito aclarar que estoy buscando una solución de metaprogtwigción que me permita modificar la instancia de la clase en tiempo de ejecución en lugar de modificar el código fuente que originalmente definió la clase. Algunas de las soluciones explican cómo declarar variables de instancia en las definiciones de clase, pero eso no es lo que estoy preguntando. Perdón por la confusion.

Creo que no entiendes bien el concepto de “clases abiertas”, lo que significa que puedes abrir una clase en cualquier momento. Por ejemplo:

 class A def hello print "hello " end end class A def world puts "world!" end end a = A.new a.hello a.world 

Lo anterior es un código Ruby perfectamente válido, y las dos definiciones de clase pueden distribuirse en múltiples archivos Ruby. Podría usar el método “define_method” en el objeto Módulo para definir un nuevo método en una instancia de clase, pero es equivalente a usar clases abiertas.

“Clases abiertas” en Ruby significa que puedes redefinir CUALQUIER clase en CUALQUIER momento en el tiempo … lo que significa agregar nuevos métodos, redefinir métodos existentes o lo que quieras realmente. Parece que la solución de “clase abierta” realmente es lo que estás buscando …

Escribí una gem para esto hace algún tiempo. Se llama “Flexible” y no está disponible a través de rubygems, pero estuvo disponible a través de github hasta ayer. Lo borré porque era inútil para mí.

Tu puedes hacer

 class Foo include Flexible end f = Foo.new f.bar = 1 

Con ello sin recibir ningún error. Por lo tanto, puede establecer y obtener variables de instancia de un objeto sobre la marcha. Si estás interesado … podría volver a subir el código fuente a github. Necesita alguna modificación para habilitar

 f.bar? #=> true 

como método para preguntar al objeto si una variable de instancia “barra” está definida o no, pero se está ejecutando algo más.

Saludos cordiales, musicmatze

Parece que todas las respuestas anteriores asumen que sabes cuál es el nombre de la clase que quieres modificar cuando estás escribiendo tu código. Bueno, eso no siempre es cierto (al menos, no para mí). Podría estar iterando sobre una stack de clases a las que quiero otorgar alguna variable (por ejemplo, mantener algunos metadatos o algo así). En ese caso, algo como esto hará el trabajo,

 # example classes that we want to tweak class Foo;end class Bar;end klasses = [Foo, Bar] # iterating over a collection of klasses klasses.each do |klass| # #class_eval gets it done klass.class_eval do attr_accessor :baz end end # it works f = Foo.new f.baz # => nil f.baz = 'it works' # => "it works" b = Bar.new b.baz # => nil b.baz = 'it still works' # => "it still works"