2008-08-14 20 views
34

Ok, he estado refacturando mi código en mi pequeña aplicación Rails en un esfuerzo por eliminar la duplicación y, en general, hacer mi vida más fácil (ya que me gusta una vida fácil). Parte de esta refactorización ha sido mover el código que es común a dos de mis modelos a un módulo que puedo incluir donde lo necesito.Ruby mixins y super métodos de llamada

Hasta ahora, todo bien. Parece que va a funcionar, pero acabo de encontrar un problema que no estoy seguro de cómo moverme. El módulo (que llamé sendable), simplemente va a ser el código que maneja el envío de faxes, el envío de correos electrónicos o la impresión de un PDF del documento. Entonces, por ejemplo, tengo una orden de compra, y tengo Órdenes de Ventas Internas (imaginativamente abreviadas a ISO).

El problema que he llamó la atención, es que quiero algunas variables inicializado (inicializar para la gente que no explican correctamente: P) después de cargar el objeto, por lo que he estado usando el gancho after_initialize. No hay problema ... hasta que empiece a agregar más mixins.

El problema que tengo, es que puedo tener un after_initialize en cualquiera de mis mixins, por lo que deberá incluir un súper llamada al principio para asegurarse de que la otra mixin after_initialize llamadas consiguen llamados . Lo cual es genial, hasta que termino llamando súper y me sale un error porque no hay super para llamar.

He aquí un pequeño ejemplo, en caso de que no he sido lo suficientemente confuso:

class Iso < ActiveRecord::Base 
    include Shared::TracksSerialNumberExtension 
    include Shared::OrderLines 
    extend Shared::Filtered 
    include Sendable::Model 

    validates_presence_of :customer 
    validates_associated :lines 

    owned_by    :customer 
    order_lines    :despatched # Mixin 

    tracks_serial_numbers :items # Mixin 

    sendable :customer      # Mixin 

    attr_accessor :address 

    def initialize(params = nil) 
    super 
    self.created_at ||= Time.now.to_date 
    end 
end 

Por lo tanto, si cada uno de los mixins tienen una llamada after_initialize, con una super llamada , ¿cómo puedo dejar que la última llamada super ¿ha provocado la aparición del error? ¿Cómo puedo probar que el súper método existe antes de que lo llame?

Respuesta

40

Puede utilizar este:

super if defined?(super) 

Aquí se muestra un ejemplo:

class A 
end 

class B < A 
    def t 
    super if defined?(super) 
    puts "Hi from B" 
    end 
end 

B.new.t 
0

en lugar de comprobar si existe el método estupendo, que sólo puede definirlo

class ActiveRecord::Base 
    def after_initialize 
    end 
end 

Esto funciona en mi prueba, y no debe romper ninguna de su código existente, ya que todos sus otras clases que definen simplemente reemplazará silenciosamente este método de todos modos

+1

downvoted porque no es una solución general. El punto es que no se sabe si el súper existe o no, por lo que monopatching ActiveRecord :: Base # after_initialize en existencia, se crea un punto donde el código se romperá si/cuando ActiveRecord agrega un Base # after_initialize, o se cambia su arity ; es mucho mejor llamarlo condicionalmente si está definido. – yaauie

+0

@yaauie - seguro, podría haber puesto un 'raise' oh no 'if methods.include? (: After_initialize) 'antes del monopatch, pero eso haría el ejemplo más difícil de entender ... Es tan fácil quedar atrapado al detallar todos los casos extremos, la lección real aquí (solo parchear un método base) se perdería en el ruido. –

+1

monkeypatching no es una solución general y no debería, en general, ser alentado. Una respuesta que involucra un monekypatch de una sola vez que * puede * pasar a funcionar conduce a un código innecesariamente complejo y frágil, especialmente si no tiene control en toda la cadena de herencia. – yaauie

3

¿Has probado alias_method_chain? Básicamente puede encadenar todas sus llamadas after_initialize. Actúa como un decorador: cada método nuevo agrega una nueva capa de funcionalidad y pasa el control al método "anulado" para hacer el resto.

3

La clase que incluye (lo que se hereda de ActiveRecord::Base, que, en este caso es Iso) podría definir su propia after_initialize, por lo que cualquier solución que no sea alias_method_chain (u otro alias que salva el original) corre el riesgo de sobrescribir código. La solución de @Orion Edwards es lo mejor que se me ocurre. Hay otros, pero son ahora más hackish.

alias_method_chain también tiene la ventaja de crear versiones con nombre del método after_initialize, lo que significa que puede personalizar el orden de las llamadas en los raros casos en que sea importante. De lo contrario, estás a merced del orden en el que la clase incluye los mixins.

tarde:

He publicado una pregunta a la lista de correo rubí sobre raíles núcleos de crear implementaciones predeterminadas vacías de todas las devoluciones de llamada. El proceso de guardado los verifica de todos modos, así que no veo por qué no deberían estar allí. El único inconveniente es crear cuadros de pila vacíos adicionales, pero eso es bastante barato en cada implementación conocida.

2

Puede acaba de lanzar una rápida condicional allí:

super if respond_to?('super') 

y que debe estar bien - no hay métodos inútiles adición; bonito y limpio.

+0

Parece que ** no ** no funciona. responder_a? ('super') siempre devolverá falso. – averell