2011-01-19 3 views
5

Я пытаюсь переопределить динамически сгенерированный метод, включив модуль.Почему включение этого модуля не отменяет динамически сгенерированный метод?

В приведенном ниже примере ассоциация Ripple добавляет метод rows= в таблицу. Я хочу называть этот метод, но потом делать некоторые дополнительные вещи.

Я создал модуль для переопределения метода, считая, что модуль row= сможет вызвать super, чтобы использовать существующий метод.

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 

Однако мой новый rows= никогда не вызывается, о чем свидетельствует тот факт, что если я поднимаю исключение внутри него, ничего не происходит.

Я знаю, что модуль входит в комплект, потому что, если я помещу его в это, мое исключение будет поднято.

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

Кроме того, если вместо rows=, модуль определяет somethingelse=, что метод является вызываемым.

Почему мой метод модуля не переопределяет динамически сгенерированный?

ответ

10

Давайте проведем эксперимент:

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 

Почему? Ответ прост:

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

B находится перед A в цепочке предков (вы можете думать об этом как B будучи внутриA). Поэтому A.x всегда имеет приоритет над B.x.

Однако это можно обойти:

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" 

С ActiveSupport (и, следовательно, Rails) вы этот шаблон реализован как 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 

Update Рубин 2 поставляется с Module#prepend , что делает переопределение методов A, что делает ненужным использование этого alias для большинства случаев использования.

+0

Я собирался подняться, но затем вы свалили и оставили всех висевших. :-) –

+0

Спасибо! Я должен был знать это: я написал цепочку наследования здесь ... http://stackoverflow.com/questions/3492679/ruby-determining-method-origins :) –

2

Почему мой метод модуля не переопределяет динамически сгенерированный?

Потому что это не так, как наследование работает. Методы, определенные в классе, переопределяют те, которые унаследованы от других классов/модулей, а не наоборот.

В Ruby 2.0, есть Module#prepend, который работает так же, как Module#include, за исключением того, что вставляет модуль как подкласса вместо суперкласса в цепочке наследования.

+0

Удивительно, я очень рад видеть модуль # prepend запланированный (по крайней мере ориентировочно) для Ruby 2 !! (См. Http://redmine.ruby-lang.org/issues/1102) –

0

Если вы используете extend экземпляр класса, вы можете это сделать.

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'