Append_features(mod)

append_features(mod)

The documentation says:

When this module is included in another, Ruby calls append_features in
this module, passing it the receiving module in mod. Rubys default
implementation is to add the constants, methods, and module variables
of this module to mod if this module has not already been added to mod
or one of its ancestors. See also Module#include.

What if this module is included in a class, will append_features of
this module still be called, passing in the class as mod? This is in
particular reference to ActiveRecord::Concern, which has its own
implementation of append_features overriding ruby’s.

1.9.3p0 :003 > class A
1.9.3p0 :004?> end
1.9.3p0 :009 > A.is_a?(Module)
=> true
1.9.3p0 :010 > A.is_a?(Class)
=> true
1.9.3p0 :011 > Class.is_a?(Module)
=> true

class is module, so answer is append_features is run

So that explains a lot.

When module B is included in class C, we extend
ActiveSupport::Concern, which invokes the extended hook in the module
Concern, passing module B as local variable base. We want to indicate
that module B is a concern, so we set an instance variable
@_dependencies on it set to an empty array. Next, since B is included
in class C, the included hook in module B is called. The included hook
of ruby is overwritten by the included hook of ActiveSupport::Concern.
It checks if base was passed as the first argument to the included
hook, if not, the block passed to the included hook in module B (which
was included in class C) is set to the @_included_block instance
variable of Concern. Finally, since a class is an instance of Module,
a class is a module. Hence, since we included module B in class C
(which itself is a Module), the append_features hook is executed. What
append_features does is that when this module is included in another,
Ruby calls append_features in this module, passing it the receiving
module in mod. ActiveSupport::Concern captures the append_features
call and implements its own definition. The class C is passed as base
to append_features when module B is included in class C, and we check
if class C has the @_dependencies instance variable defined. Since we
did not extend ActiveSpport::Concern in class C, it does not have that
instance variable set, so the else clause is, in effect, triggered.
What we then do is iterate through each of the modules stored in
@_dependencies and include them in class C (base). We can have
multiple modules in the @_dependencies instance variable, and we have
them all included in base here, so that if one depends on another,
they are all available now in class C. It then extends ClassMethods
and includes InstanceMethods if they are defined in module B. It
finally invokes class_eval method on base (class C), passing in the
instance variable @_included_block if it is defined. @_included_block
stores the block that we passed into included call in module B, if we
did not pass an argument (other than the block) to the included call.
So that block gets evaluated in the class context of C, and hence we
can call class methods within that block. Next notice that module A is
included in module B. Ruby now evaluates module A. The extended method
is called, and it points to ActiveSupport::Concern again. So the
extended hook of Concern is invoked, we pass module A as local
variable base, and we set the @_dependencies instance variable to an
empty array. However, there is no included hook in module A, so that
is skipped. Ruby then invokes append_features, since module A was
included in module B. appended_features gets passed the receiving
module (module B) in module A context. We check if module B has the
@_dependencies instance method defined. It does actually (since
remember in module B we extended ActiveSupport::Concern which we did
not do in class A). So since module B has that instance variable set,
we simply include self (module A) into the array of modules held in
@_dependencies array. Hence, we can have a ton of modules stored in
@_dependencies by following the same process. Ultimately, all of the
modules will be included in the first module or class that does not
extend ActiveSupport::Concern. The reason why this is done is so that
module dependencies will be included when the module is included in
the parent class (or module). Apparently, ruby has no built in utility
to accomplish dependency resolution when one module depends on others
and that one module is included in a class.