2011-01-19 11 views
5

Estoy intentando anular un método generado dinámicamente incluyendo un módulo.¿Por qué incluir este módulo no anula un método generado dinámicamente?

En el siguiente ejemplo, una asociación de Ondulación agrega un método rows= a la Tabla. Quiero llamar a ese método, pero también hacer algunas cosas adicionales después.

Creé un módulo para anular el método, pensando que el módulo row= podría llamar al super para usar el método existente.

class Table 

    # Ripple association - creates rows= method 
    many :rows, :class_name => Table::Row 

    # Hacky first attempt to use the dynamically-created 
    # method and also do additional stuff - I would actually 
    # move this code elsewhere if it worked 
    module RowNormalizer 
    def rows=(*args) 
     rows = super 
     rows.map!(&:normalize_prior_year) 
    end 
    end 
    include RowNormalizer 

end 

Sin embargo, mi nuevo rows= nunca es llamado, como lo demuestra el hecho de que si levanto una excepción dentro de él, no pasa nada.

Sé que el módulo se está incluyendo, porque si pongo esto en él, mi excepción aumenta.

 included do 
     raise 'I got included, woo!' 
     end 

También, si en lugar de rows=, el módulo define somethingelse=, que el método es exigible.

¿Por qué mi método de módulo no reemplaza al generado dinámicamente?

Respuesta

10

Vamos a hacer un experimento:

class A; def x; 'hi' end end 
module B; def x; super + ' john' end end 
A.class_eval { include B } 

A.new.x 
=> "hi" # oops 

¿Por qué? La respuesta es sencilla:

A.ancestors 
=> [A, B, Object, Kernel, BasicObject] 

B es A antes en la cadena de antepasados ​​(se puede pensar en esto como B estar dentro A). Por lo tanto, A.x siempre tiene prioridad sobre B.x.

Sin embargo, esto se puede evitar:

class A 
    def x 
    'hi' 
    end 
end 

module B 
    # Define a method with a different name 
    def x_after 
    x_before + ' john' 
    end 

    # And set up aliases on the inclusion :) 
    # We can use `alias new_name old_name` 
    def self.included(klass) 
    klass.class_eval { 
     alias :x_before :x 
     alias :x :x_after 
    } 
    end 
end 

A.class_eval { include B } 

A.new.x #=> "hi john" 

Con ActiveSupport (y, por tanto, Pasamanos) ha implementado este patrón como alias_method_chain(target, feature)http://apidock.com/rails/Module/alias_method_chain:

module B 
    def self.included(base) 
    base.alias_method_chain :x, :feature 
    end 

    def x_with_feature 
    x_without_feature + " John" 
    end 
end 

actualización Ruby 2 viene con Module#prepend , que anula los métodos de A, por lo que este truco alias es innecesario para la mayoría de los casos de uso.

+0

Estaba a punto de votar, pero luego se apagó y dejó a todos colgados. :-) –

+0

Gracias! Debería haber sabido esto: escribí la cadena de herencia aquí ... http://stackoverflow.com/questions/3492679/ruby-determining-method-origins :) –

2

¿Por qué mi método de módulo no reemplaza al generado dinámicamente?

Porque así no es como funciona la herencia. Los métodos definidos en una clase anulan los heredados de otras clases/módulos, y no al revés.

en Ruby 2.0, hay Module#prepend, que funciona igual que Module#include, excepto que inserta el módulo como una subclase en lugar de una superclase en la cadena de herencia.

+0

Impresionante, estoy realmente emocionado de ver el Módulo # preceder planeado (al menos tentativamente) para Ruby 2 !! (Ver http://redmine.ruby-lang.org/issues/1102) –

0

Si usted extend la instancia de la clase, podrá hacerlo.

class A 
    def initialize 
    extend(B) 
    end 
    def hi 
    'hi' 
    end 
end 
module B 
    def hi 
    super[0,1] + 'ello' 
    end 
end 

obj = A.new 
obj.hi #=> 'hello' 
Cuestiones relacionadas