6

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

在下面的示例中,Ripple 关联将rows=方法添加到 Table。我想调用那个方法,但之后还要做一些额外的事情。

我创建了一个模块来覆盖该方法,认为该模块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

但是,我的 newrows=从未被调用,事实证明,如果我在其中引发异常,则什么也不会发生。

我知道该模块已被包含在内,因为如果我将其放入其中,则会引发异常。

      included do
        raise 'I got included, woo!'
      end

此外,如果rows=模块定义了somethingelse=,而不是 ,则该方法是可调用的。

为什么我的模块方法没有覆盖动态生成的方法?

4

3 回答 3

11

让我们做一个实验:

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]

BA在祖先链中之前(您可以将其视为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

Update Ruby 2 附带了Module#prepend,它确实覆盖了 的方法,因此对于大多数用例来说A,这种hack 是不必要的。alias

于 2011-01-19T22:39:10.640 回答
2

为什么我的模块方法没有覆盖动态生成的方法?

因为这不是继承的工作方式。类中定义的方法会覆盖从其他类/模块继承的方法,而不是相反。

在 Ruby 2.0 中,有Module#prepend,它的工作方式与 类似Module#include,只是它将模块作为子类而不是超类插入到继承链中。

于 2011-01-19T22:52:45.803 回答
0

如果你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'
于 2011-01-19T22:56:05.017 回答