我试图通过包含一个模块来覆盖动态生成的方法.
在下面的示例中,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
但是,我的新行=永远不会被调用,事实证明,如果我在其中引发异常,则没有任何反应.
我知道该模块已被包含在内,因为如果我把它放入其中,我的异常会被提升.
- included do
- raise 'I got included,woo!'
- end
解决方法
我们来做一个实验:
- 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的方法,使得这个别名hack对大多数用例来说都是不必要的.