2012-09-25 24 views
9

¿Hay alguna manera limpia de inicializar variables de instancia en un Módulo destinado a ser utilizado como Mixin? Por ejemplo, tengo el siguiente:Inicializando variables de instancia en Mixins

module Example 

    def on(...) 
    @handlers ||= {} 
    # do something with @handlers 
    end 

    def all(...) 
    @all_handlers ||= [] 
    # do something with @all_handlers 
    end 

    def unhandled(...) 
    @unhandled ||= [] 
    # do something with unhandled 
    end 

    def do_something(..) 
    @handlers  ||= {} 
    @unhandled ||= [] 
    @all_handlers ||= [] 

    # potentially do something with any of the 3 above 
    end 

end 

en cuenta que tengo que comprobar una y otra vez si cada @member se ha inicializado correctamente en cada función - esto es ligeramente irritante. Preferiría escribir:

module Example 

    def initialize 
    @handlers  = {} 
    @unhandled = [] 
    @all_handlers = [] 
    end 

    # or 
    @handlers = {} 
    @unhandled = [] 
    # ... 
end 

Y no tiene que asegurarse repetidamente de que las cosas se hayan inicializado correctamente. Sin embargo, por lo que puedo decir, esto no es posible. ¿Hay alguna forma de evitar esto, además de agregar un método initialize_me al Example y llamar al initialize_me desde la Clase extendida? Vi this example, pero no hay forma de que esté parcheando cosas en Class solo para lograr esto.

Respuesta

12
module Example 
    def self.included(base) 
    base.instance_variable_set :@example_ivar, :foo 
    end 
end 

Editar: Tenga en cuenta que este es establecer una variable de instancia de clase. Las variables de instancia en la instancia no se pueden crear cuando el módulo se mezcla en la clase, ya que esas instancias aún no se han creado. Puede, sin embargo, crear un método initialize en el mixin, por ejemplo .:

module Example 
    def self.included(base) 
    base.class_exec do 
     def initialize 
     @example_ivar = :foo 
     end 
    end 
    end 
end 

Puede haber una manera de hacer esto mientras llama al método initialize de la clase que incluye (a alguien?). No es seguro. Pero aquí hay una alternativa:

class Foo 
    include Example 

    def initialize 
    @foo = :bar 
    after_initialize 
    end 
end 

module Example 
    def after_initialize 
    @example_ivar = :foo 
    end 
end 
+0

Excelente, gracias - Me pregunto por qué no vi este enfoque mencionado en cualquier lugar a pesar de que hay un millón de artículos al respecto. –

+0

Desafortunadamente, esto no parece funcionar: hacer referencia a '@ example_ivar' luego vuelve como' nil'. –

+0

Ah, ya veo, gracias, pero esto causará problemas si la clase ya especifica un método 'initialize', ¿verdad? –

2

modules proporciona ganchos, como Module#included. Le sugiero que consulte el documento de Ruby sobre el tema, o use ActiveSupport::Concern, que proporciona algunos ayudantes en los módulos.

+0

No estoy usando 'ActiveSupport', pero gracias por apuntarme en la dirección correcta. –

+0

de nada. – ksol

1

Tal vez esto es un poco hacky, pero se puede utilizar prepend para conseguir el comportamiento deseado:

module Foo 
    def initialize(*args) 
    @instance_var = [] 
    super 
    end 
end 

class A 
    prepend Foo 
end 

Aquí está la salida de la consola:

2.1.1 :011 > A.new 
=> #<A:0x00000101131788 @instance_var=[]> 
+0

¿Hay alguna desventaja al usar prepend en lugar de include? –

+0

El único inconveniente que se me ocurre es que el módulo se inserta debajo de la clase en la jerarquía. Normalmente, nuestra jerarquía de tipos se vería como 'A Max

+0

bien :) Bueno, "preceder" se explica por sí mismo al respecto, por lo que me gusta (y uso) su solución, ¡gracias! (+1'd) –

0

Creo que puede ser una respuesta más simple a esto. El módulo debe tener un inicializador que inicialice las variables como lo haría normalmente. En el inicializador para la clase que incluye el módulo, invoque a super() para invocar el inicializador en el módulo incluido. Esto simplemente sigue las reglas de envío de métodos en Ruby.

Reflexionando, esto no funcionará tan bien si la clase que incluye el módulo también tiene una superclase que necesita ser inicializada. El inicializador en el módulo necesitaría aceptar una lista de parámetros variables y pasarla a la superclase. Aunque parece una buena avenida para explorar.