2009-12-14 12 views
5

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 ​​

  1. 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'.
  2. 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)
  3. 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 
+2

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. –

Respuesta

1

Obviamente, existe un peligro en reemplazar totalmente la definición de clase con una nueva definición de clase, ya sea que esté fusionando la nueva versión o eliminando la versión anterior y esperando que los objetos se actualicen automáticamente. Ese peligro radica en el hecho de que la versión anterior del objeto puede estar en un estado no válido para la nueva versión.(Por ejemplo, las variables de instancia que la nueva versión de los initializies de clase en su método initialize pueden no han sido definidos por la versión antigua, pero podría ser aso errores más sutiles que esto). Por lo tanto, se necesita cuidado (y una ruta de actualización bien planificada) sin importar cómo se realice esto.

Dado que usted sabe cómo se ve la versión que está actualizando (que necesita para actualizar sensiblemente de todos modos), es muy simple tener la nueva versión de la clase eliminar los métodos innecesarios de la versión anterior de la clase:

class A 
    remove_method :foo 
end 

Y no estoy seguro de lo que está hablando cuando se dice que hay problemas redefinición de un método para tomar un número diferente de parámetros. Funciona bien para mí:

class A 
    def foo a 
    a 
    end 
end 
ainst=A.new 
p(ainst.foo 1) rescue puts($!) 
p(ainst.foo 1,2) rescue puts($!) 

class A 
    def foo a,b 
    [a,b] 
    end 
end 
p(ainst.foo 1) rescue puts($!) 
p(ainst.foo 1,2) rescue puts($!) 

La única cosa que no puede hacer (que yo sepa) es cambiar la superclase de la clase. Eso se define la primera vez que define la clase, y no tiene permitido cambiarla (aunque puede especificar la misma clase antecesora de nuevo).

class A < Object 
end 
class A < Object 
end 
class A < String #TypeError: superclass mismatch for class A 
end 
+0

Estoy totalmente de acuerdo con usted, la actualización debe estar bien planificada y controlada. Entonces, si se elimina un método, la nueva versión debería eliminarlo explícitamente (por remove_method). El problema con la firma del método (cambio de número de argumentos) estaba de hecho relacionado con la falta de coincidencia de la versión entre el que llama y esta clase. Pero reflexionando sobre eso, esto también debería estar contenido con la ruta de actualización "controlada". No debería haber ninguna discrepancia en la versión, incluso fuera de esta clase. – bryantsai

1

En resumen, no es no hay forma de hacer esto sin una piratería seria. Lo que sugiero que haga es crear un método to_serialized que devuelva una matriz que el método initialize acepte para obtener el mismo estado. Si simplemente desea copiar todas las variables de instancia más, usted puede hacer esto:

class A 
    def initialize(instance_variables) 
    instance_variables.each do |key, value| 
     self.instance_variable_set(key, value) 
    end 
    end 

    def to_serialized 
    iv = {} 
    self.instance_variables.each do |key| 
     iv[key] = self.instance_variable_get(key) 
    end 
    end 
end 

Y para recargar el método, usted puede hacer esto:

obj_state = object.to_serialized 
Object.send(:remove_const, 'A') 
load 'file.rb' 
object = A.new(obj_state) 

Tenga en cuenta que esto no hace nido, por lo si alguno de los objetos a los que se refieren las variables de instancia también se recarga, debe "serializarlos" usted mismo.

Cuestiones relacionadas