Module including other module

Simplified Code: gist:4563204 · GitHub

I am trying to understand why when I reopen a module A and ‘include’ a
module B, class Klass that has already included module A do not gain the
new methods from module B.

When I do reopen module A and add a new method, class Klass that has
already included module A gain these methods.

What is the difference between reopening a module and adding a method
and
including another module?

hmmm…

not sure what’s going on there, i’d expect the same behavior that you
do. you’ve probably already found ways to work around this, but if not,
both of these work:

module A
def foo1; end
end

module B
def foo2; end
end

module A
include B
def foo3; end
end

class Klass
include A
end

p Klass.instance_methods

**** or ****

module A
def foo1; end
end

class Klass
include A
end

module B
def foo2; end
end

class Klass
include B
end

module A
def foo3; end
end

p Klass.instance_methods

wish i could explain (and understand!) why your original example
doesn’t work as expected…

  • j

I’ve updated the gist a bit to reflect what you found about moving the
class decoration below the module A reopening.

This is just really baffling =(

For a concrete example, if I wrote an extension module SuperEnum to
Enumerable and wanted all objects that included Enumerable to have
access
to this extension, I would expect that I could open up the module
Enumerable and include module SuperEnum.

I asked about this to Matz in the past and it works that way basically
as a
consequence of the implementation.

As you probably know, MRI stores a linear flat chain of ancestors of
a class or module. Each ancestor has a pointer to its own direct
ancestor
in the chain of that particular class or module conceptually (there are
indirections but they are not important).

Thing is, albeit new methods are found because method name resolution
follows the pointers at the time of the call (conceptually), and albeit
if
you include another module in the very class the pointers are
adjusted, when you include a 2nd order module the ancestor chains of the
classes or modules the enclosing module was already mixed in are not
updated and reflattened so to speak.

Indeed, modules just do not keep that information, they do not know
where
they have been included. So that update is not even possible with the
current implementation.

Matz said he would be willing to make this work provided the
implementation
had no performance penalty.

Sent from my iPhone

Ahh, that makes sense! Thanks Xavier!

not sure i understood correctly, but this works -

module SuperEnum
def super_enum_method; end
end

module Enumerable
include SuperEnum
end

class Foo
include Enumerable
end

p Foo.instance_methods.include?(:super_enum_method)

=> true