Почему включение этого модуля не отменяет динамически сгенерированный метод?
Я пытаюсь переопределить динамически генерируемый метод, включив модуль.
В приведенном ниже примере ассоциация 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=
, этот метод можно вызвать.
Почему мой метод модуля не переопределяет динамически сгенерированный?
Ответы
Ответ 1
Сделайте эксперимент:
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
Обновление. Ruby 2 поставляется с Module # prepend, который переопределяет методы A
, делая это alias
взломать ненужные для большинства случаев использования.
Ответ 2
Почему мой метод модуля не переопределяет динамически сгенерированный?
Потому что это не так, как работает наследование. Методы, определенные в классе, переопределяют те, которые унаследованы от других классов/модулей, а не наоборот.
В Ruby 2.0 существует Module#prepend
, который работает точно так же, как Module#include
, за исключением того, что он вставляет модуль в качестве подкласса вместо суперкласса в цепочке наследования.
Ответ 3
Если вы 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'