2011-01-19 13 views
5

Sto provando a sovrascrivere un metodo generato dinamicamente includendo un modulo.Perché includere questo modulo non sovrascrive un metodo generato dinamicamente?

Nell'esempio seguente, un'associazione Ripple aggiunge un metodo rows= a Tabella. Voglio chiamare quel metodo, ma anche fare alcune cose aggiuntive in seguito.

Ho creato un modulo per sovrascrivere il metodo, pensando che il modulo row= sia in grado di chiamare super per utilizzare il metodo esistente.

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 

Tuttavia, il mio nuovo rows= non viene mai chiamato, come dimostra il fatto che se alzo un'eccezione al suo interno, non succede nulla.

So che il modulo viene incluso, perché se inserisco questo, la mia eccezione viene sollevata.

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

Inoltre, se invece di rows=, il modulo definisce somethingelse=, tale metodo è richiamabile.

Perché il metodo del modulo non ha la precedenza su quello generato dinamicamente?

risposta

10

Facciamo un esperimento:

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 

Perché? La risposta è semplice:

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

B è prima di A nella catena antenati (si può pensare a questo come B essendo all'internoA). Pertanto, A.x ha sempre la priorità su B.x.

Tuttavia, questo può essere aggirato:

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 (e quindi Rails) è stato questo schema implementato come 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 

Aggiornamento Ruby 2 è dotato di Module#prepend , che sovrascrive i metodi di A, rendendo questa incisione alias non necessaria per la maggior parte dei casi d'uso.

+0

Stavo per alzarmi, ma poi ti sei fermato e hai lasciato tutti appesi. :-) –

+0

Grazie! Avrei dovuto saperlo: ho scritto la catena di ereditarietà qui ... http://stackoverflow.com/questions/3492679/ruby-determining-method-origins :) –

2

Perché il metodo del modulo non ha la precedenza su quello generato dinamicamente?

Perché non è così che funziona l'ereditarietà. I metodi definiti in una classe sovrascrivono quelli ereditati da altre classi/moduli, non il contrario.

In Ruby 2.0, c'è Module#prepend, che funziona come Module#include, tranne che inserisce il modulo come sottoclasse invece di una superclasse nella catena di ereditarietà.

+0

Impressionante, sono davvero entusiasta di vedere il modulo n. pianificato (almeno provvisoriamente) per Ruby 2 !! (Vedi http://redmine.ruby-lang.org/issues/1102) –

0

Se si è extend l'istanza della classe, è possibile farlo.

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' 
Problemi correlati