programacion - ruby on rails poo
Método recomendado para parchar una clase en rubí. (2)
Me he dado cuenta de que hay dos formas comunes de parchear una clase en ruby:
Defina los nuevos miembros en la clase de esta manera:
class Array
def new_method
#do stuff
end
end
Y llamando class_eval en el objeto de clase:
Array.class_eval do
def new_method
#do stuff
end
end
Me pregunto si hay alguna diferencia entre los dos y si hay ventajas al usar un enfoque sobre el otro.
Honestamente, solía usar la primera forma (reabrir la clase), ya que se siente más natural, pero su pregunta me obligó a hacer una investigación sobre el tema y aquí está el resultado.
El problema con la reapertura de la clase es que definirá silenciosamente una nueva clase si la original, que intentaba reabrir, por alguna razón no se definió en este momento. El resultado puede ser diferente:
Si no reemplaza ningún método, solo agrega los nuevos y se define la implementación original (por ejemplo, el archivo donde se carga la clase originalmente se carga) más tarde, todo estará bien.
Si redefine algunos métodos y el original se carga más tarde, sus métodos se anularán nuevamente con sus versiones originales.
El caso más interesante es cuando utiliza la autoloading estándar o algún mecanismo de recarga sofisticado (como el que se usa en los rieles) para cargar / recargar clases. Algunas de estas soluciones se basan en const_missing que se llama cuando se hace referencia a una constante indefinida. En ese caso, el mecanismo de carga automática intenta encontrar una definición de clase no definida y cargarla. Pero si está definiendo la clase por su cuenta (aunque tenía la intención de reabrir una ya definida), ya no se "perderá" y es posible que el original nunca se cargue, ya que el mecanismo de carga automática no se activará.
Por otro lado, si usa class_eval
, se le notificará instantáneamente si la clase no está definida en este momento. Además, como hace referencia a la clase cuando llama a su método class_eval
, cualquier mecanismo de carga automática tendrá la oportunidad de ubicar la definición de la clase y cargarla.
Teniendo eso en cuenta, class_eval
parece ser un mejor enfoque. Sin embargo, estaría feliz de escuchar alguna otra opinión.
Alcance
Una gran diferencia que, creo, KL-7 no señaló es el alcance en el que se interpretará su nuevo código:
Si está (re) abriendo una clase para manipularla, el nuevo código que agregue se interpretará en el ámbito de la clase (original).
Si está utilizando el Module#class_eval para manipular una clase, el nuevo código que agregue se interpretará en el ámbito que rodea su llamada a #class_eval y NO será consciente del alcance de clase. Si uno no lo sabe, este comportamiento podría ser contraintuitivo y conducir a errores difíciles de depurar.
CONSTANT = ''surrounding scope''
# original class definition (uses class scope)
class C
CONSTANT = ''class scope''
def fun() p CONSTANT end
end
C.new.fun # prints: "class scope"
# monkey-patching with #class_eval: uses surrounding scope!
C.class_eval do
def fun() p CONSTANT end
end
C.new.fun # prints: "surrounding scope"
# monkey-patching by re-opening the class: uses scope of class C
class C
def fun() p CONSTANT end
end
C.new.fun # prints: "class scope"