Why/how does ActiveSupport::Concern change ancestor lookup order?

时间:2016-10-15 17:16:51

标签: ruby activesupport-concern

Consider the following code:

require 'active_support/concern'

module Inner
end

module Outer
  extend ActiveSupport::Concern
  included do
    include Inner
  end
end

class FirstClass
  include Outer
end

module SecondInner
end

module SecondOuter
  include SecondInner
end

class SecondClass
  include SecondOuter
end

Why is the ancestor order different for modules included via AS::Concern vs. plain-old Ruby?

FirstClass.ancestors
# => [FirstClass, Inner, Outer, Object, PP::ObjectMixin, Kernel, BasicObject]

SecondClass.ancestors
# => [SecondClass, SecondOuter, SecondInner, Object, PP::ObjectMixin, Kernel, BasicObject]

2 个答案:

答案 0 :(得分:4)

ActiveSupport::Concern不会更改祖先查找顺序。

如果你改变module Outer使用纯Ruby来做同样的事情,AS会做什么,但没有AS,你会看到它有相同的祖先链:

module Outer
  def self.included(base)
    base.send(:include, Inner)
  end
end

SecondClass.ancestors
#=> [SecondClass, SecondOuter, SecondInner, Object, PP::ObjectMixin, Kernel, BasicObject]

答案 1 :(得分:0)

Andrey Deineko的回答为理解正在发生的事情提供了重要的基础。

当模块包含在类或其他模块中时会发生什么?有两件事似乎相关:

  • append_features被召唤。
  

当这个模块包含在另一个模块中时,Ruby在这个模块中调用append_features,并在mod中传递接收模块。 Ruby的默认实现是将此模块的常量,方法和模块变量添加到mod,如果此模块尚未添加到mod或其祖先之一。另请参见模块#include。

  • included被称为
  

只要接收器包含在另一个模块或类中,就会调用回调。如果您的代码想要在模块包含在另一个模块中时执行某些操作,则应该优先使用Module.append_features。

我们无法挂钩append_features但我们可以在模块中定义included

module Inner
  def self.included(base)
    puts "including #{self} in #{base}"
  end
end

module Outer
  def self.included(base)
    puts "including #{self} in #{base}"
    base.send(:include, Inner)
  end
end

module SecondOuter
  include Inner
  def self.included(base)
    puts "including #{self} in #{base}"
  end
end

class FirstClass
  include Outer
end

class SecondClass
  include SecondOuter
end

Outer和SecondOuter之间的区别在于Inner的使用方式。在Outer中,Inner包含 ,但仅包含在包含Outer的其他模块中。但是,在SecondOuter中,Inner 包含

将上述代码粘贴到控制台中时,屏幕上会显示以下语句:

including Inner in SecondOuter
including Outer in FirstClass
including Inner in FirstClass
including SecondOuter in SecondClass

第一和第四个陈述解释了SecondClass祖先的顺序。 InnerSecondOuter的祖先,SecondClass的祖先是SecondOuter.ancestors => [SecondOuter, Inner] SecondClass.ancestors => [SecondClass, SecondOuter, Inner, Object, PP::ObjectMixin, Kernel, BasicObject] 的祖先。因此我们有

FirstClass

第三和第四个陈述说明为什么Inner的祖先的外部和内部模块顺序是相反的:

首先,Outer Outer的祖先。

其次,FirstClassInner之前包含Inner.included,但 Outer.includedOuter.ancestors => [Outer] FirstClass.ancestors => [FirstClass, Inner, Outer, Object, PP::ObjectMixin, Kernel, BasicObject] 之前解析。这导致

include SomeModule

当我们扩展AS :: Concern并在included do块中添加SomeModule语句时,我们实际上包括Outer类似于上面{{1}}的方式。< / p>

Ruby 2.3.1 Module docs

ActiveSupport::Concern included