为什么包含此模块不会覆盖动态生成的方法?

我试图通过包含一个模块来覆盖动态生成的方法。

在下面的示例中,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'