为什么包含此模块不会覆盖动态生成的方法? [英] Why does including this module not override a dynamically-generated method?
问题描述
我试图通过包含一个模块来覆盖动态生成的方法.
I'm trying to override a dynamically-generated method by including a module.
在下面的示例中,Ripple 关联将 rows=
方法添加到 Table.我想调用那个方法,但之后还要做一些额外的事情.
In the example below, a Ripple association adds a rows=
method to Table. I want to call that method, but also do some additional stuff afterwards.
我创建了一个模块来覆盖该方法,认为该模块的 row=
将能够调用 super
以使用现有方法.
I created a module to override the method, thinking that the module's row=
would be able to call super
to use the existing method.
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=
从未被调用,事实证明,如果我在其中引发异常,则什么也不会发生.
However, my new rows=
is never called, as evidenced by the fact that if I raise an exception inside it, nothing happens.
我知道该模块已包含在内,因为如果我将其放入其中,则会引发异常.
I know the module is getting included, because if I put this in it, my exception gets raised.
included do
raise 'I got included, woo!'
end
此外,如果模块定义了 somethingelse=
而不是 rows=
,则该方法是可调用的.
Also, if instead of rows=
, the module defines somethingelse=
, that method is callable.
为什么我的模块方法没有覆盖动态生成的方法?
Why isn't my module method overriding the dynamically-generated one?
推荐答案
我们来做个实验:
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
这是为什么?答案很简单:
Why is that? The answer is simple:
A.ancestors
=> [A, B, Object, Kernel, BasicObject]
B
在祖先链中位于 A
之前(您可以将其视为 B
在 inside <代码>A).因此 A.x
总是优先于 B.x
.
B
is before A
in the ancestors chain (you can think of this as B
being inside A
). Therefore A.x
always takes priority over 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:
With ActiveSupport (and therefore Rails) you have this pattern implemented as 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
hack 对于大多数用例来说是不必要的.
Update Ruby 2 comes with Module#prepend, which does override the methods of A
, making this alias
hack unnecessary for most use cases.
这篇关于为什么包含此模块不会覆盖动态生成的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!