Почему / как ActiveSupport::Concern изменяет порядок поиска предков?

#ruby #activesupport-concern

#ruby #activesupport-concern

Вопрос:

Рассмотрим следующий код:

 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
 

Почему порядок предков отличается для модулей, включенных через AS::Concern, по сравнению с обычным Ruby?

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

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

Ответ №1:

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. Я хочу сказать, что синтаксический сахар, предоставляемый AS, не влияет на поиск предков Ruby.

2. Хорошо, так почему порядок отличается в этом случае? Имеет ли это какое-то отношение к тому, когда append_feature вызывается в каждом случае?

3.@jcm это на самом деле вполне может иметь какое-то отношение к этому, поскольку include вызывает Module.append_features каждый параметр в обратном порядке.

Ответ №2:

Ответ Андрея Дейнеко послужил важной основой для понимания того, что происходит.

Что происходит, когда модули включаются в классы или другие модули? Есть две вещи, которые кажутся актуальными:

  • append_features вызывается.

Когда этот модуль включается в другой, Ruby вызывает append_features в этом модуле, передавая ему принимающий модуль в mod. Реализация Ruby по умолчанию заключается в добавлении констант, методов и переменных модуля этого модуля в mod, если этот модуль еще не был добавлен в mod или один из его предков. Смотрите также Module#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 используется. In Outer , Inner не включен, а просто включен в любой другой модуль, который включает Outer . Тем SecondOuter не менее, Inner включено.

Когда приведенный выше код вставляется в консоль, на экране выводятся следующие инструкции:

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

Первый и четвертый операторы объясняют порядок SecondClass предков. Inner является предком SecondOuter , который, в свою очередь, является предком SecondClass . Таким образом, мы имеем

 SecondOuter.ancestors
=> [SecondOuter, Inner]

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

Третий и четвертый операторы показывают, почему внешний и внутренний порядок модулей меняются местами для FirstClass предков:

Во-первых, Inner не является предком Outer .

Во-вторых, Outer включается в FirstClass before Inner is , но Inner.included разрешается до Outer.included does . Это приводит к

 Outer.ancestors
=> [Outer]

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

Когда мы расширяем AS::Concern и помещаем include SomeModule оператор в included do блок, мы фактически включаем SomeModule аналогично тому, как Outer это делается выше.

Документы модуля Ruby 2.3.1

ActiveSupport::Concern included