为什么包含此模块不会覆盖动态生成的方法? [英] Why does including this module not override a dynamically-generated method?

查看:40
本文介绍了为什么包含此模块不会覆盖动态生成的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 之前(您可以将其视为 Binside <代码>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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆