为什么包含此模块不会覆盖动态生成的方法?
我试图通过包含一个模块来覆盖动态生成的方法。
在下面的示例中,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=
永远不会被调用,事实certificate,如果我在其中引发exception,则没有任何反应。
我知道该模块已被包含在内,因为如果我把它放入其中,我的exception会被提升。
included do raise 'I got included, woo!' end
此外,如果不是rows=
,则模块定义somethingelse=
,该方法是可调用的。
为什么我的模块方法不会覆盖动态生成的模块?
我们来做一个实验:
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
)。 因此, Ax
总是优先于Bx
。
但是,这可以解决:
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
hack对大多数用例都是不必要的。
为什么我的模块方法不会覆盖动态生成的模块?
因为这不是inheritance的工作方式。 类中定义的方法会覆盖从其他类/模块inheritance的方法,而不是相反。
在Ruby 2.0中,有Module#prepend
,它就像Module#include
Module#prepend
一样,除了它将模块作为子类而不是inheritance链中的超类插入。
如果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'