Hice algunos experimentos con la carga/descarga/actualización dinámica de la clase Ruby como implementación de la infraestructura de complementos. Encontré algunos puntos:carga/descarga/actualización de clase en ruby
- Si carga la nueva versión de la misma clase sin descargarla primero, la nueva esencialmente 'arriba' o 'fusionar' con la versión anterior. Todos los objetos existentes creados con la versión anterior obtendrían la definición de su clase 'actualizada'.
- La descarga de una clase no afecta a los objetos existentes creados con esta clase. Los objetos existentes permanecen con la versión que se acaba de descargar. (la clase no puede usarse más pero no los objetos ya creados)
- Si carga una nueva versión después de la descarga de la versión anterior, los nuevos objetos creados serían de la nueva versión. Sin embargo, los objetos viejos creados antes de la carga de la nueva versión no se verían afectados y seguirían siendo de la versión anterior.
Mi pregunta es, ¿hay una manera fácil de hacer objeto existente creado a partir de la versión antigua clase 'interruptor' a la nueva versión (pero no una versión fusionada de la vieja & nueva versión)? Me parece que la forma posible de hacerlo es volver a crear el objeto después de la descarga/carga, que no es adecuado para complementos (no quiero que se destruya).
actualización: mi intención era tener objetos existentes actualizados con la nueva versión, sin el problema de la fusión versión antigua con la nueva versión (como el cambio del número de argumentos, o la extracción de un método). La descarga y la recarga nuevamente parece ser la forma más limpia de hacerlo, aunque debe hacer un seguimiento de todos esos objetos y recrearlos cuando sea necesario. Además, los objetos caros pueden no ser adecuados para la recreación. Esto me deja con la segunda opción, que prohíbe que ocurra una fusión inesperada. Siempre que no se elimine ningún método, no se cambie la firma del método, la fusión debería funcionar bien.
A continuación es mi programa de pruebas:
$ cat test.rb
load 'v1.rb'
puts "=> 'v1.rb' loaded"
a1 = A.new
puts "=> object a1(#{a1}) created"
a1.common
a1.method_v1
load 'v2.rb'
puts '',"=> class A updated by 'v2.rb'"
a1.common
a1.method_v1
a1.method_v2
a2 = A.new
puts '',"=> object a2(#{a2}) created"
a2.common
a2.method_v1
a2.method_v2
Object.send(:remove_const, 'A')
puts '',"=> class A unloaded"
A.new rescue puts $!
puts '',"=> class A does not exist now"
a1.common
a1.method_v1
a1.method_v2 rescue puts $!
a2.common
a2.method_v1
a2.method_v2
load 'v3.rb'
puts '',"=> 'v3.rb' loaded"
a1.common
a1.method_v1
a1.method_v2 rescue puts $!
a1.method_v3 rescue puts $!
a2.common
a2.method_v1
a2.method_v2
a2.method_v3 rescue puts $!
a3 = A.new
puts '',"=> object a3(#{a3}) create"
a3.common
a3.method_v1 rescue puts $!
a3.method_v2 rescue puts $!
a3.method_v3
El resultado de ejemplo:
$ ruby test.rb
=> 'v1.rb' loaded
=> object a1(#<A:0x1042d4b0>) created
#<A:0x1042d4b0>: common: v1
#<A:0x1042d4b0>: method v1
=> class A updated by 'v2.rb'
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
=> object a2(#<A:0x1042cec0>) created
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
=> class A unloaded
uninitialized constant A
=> class A does not exist now
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
=> 'v3.rb' loaded
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
undefined method `method_v3' for #<A:0x1042d4b0>
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
undefined method `method_v3' for #<A:0x1042cec0>
=> object a3(#<A:0x1042c3f8>) create
#<A:0x1042c3f8>: common: v3
undefined method `method_v1' for #<A:0x1042c3f8>
undefined method `method_v2' for #<A:0x1042c3f8>
#<A:0x1042c3f8>: method v3
A continuación se muestra las 3 versiones de la clase A:
$ cat v1.rb
class A
def common
puts "#{self}: common: v1"
end
def method_v1
puts "#{self}: method v1"
end
end
$ cat v2.rb
class A
def common
puts "#{self}: common: v2"
end
def method_v2
puts "#{self}: method v2"
end
end
$ cat v3.rb
class A
def common
puts "#{self}: common: v3"
end
def method_v3
puts "#{self}: method v3"
end
end
En realidad, no ha descargado la clase A. La ha desconectado de su nombre, pero las instancias existentes de la clase A todavía contienen referencias a la clase. La clase se elimina cuando se puede recolectar basura, lo que ocurre cuando todas las instancias de la clase son basura recolectada. –