blob
blob

Reputation: 447

Ruby - including single module multiple times and ancestry hierarchy

I'm reading 'Metaprogramming Ruby' right now and writing some code at the same time to clarify the concepts. I've read that when you include/prepend a single module multiple times, all further inclusions won't change the place of said module in the ancestry chain.

I wrote some code that works in a way that I did not expect - what actually happens there?

module GreatGrandfather; end

module Grandfather
  include GreatGrandfather
end

module Father
  include Grandfather
  prepend GreatGrandfather
end

module Son
  include Father
end

Son.ancestors # => [Son, Father, Grandfather, GreatGrandfather]

I've assumed that when I run Son.ancestors, Son would include Father, Father would include Grandfather and prepend GreatGrandfather and the ancestry tree would be set to [Son, GreatGrandfather, Father, Grandfather]. Obviously that didn't happen.

Once Son includes Father, it starts to look in the Father module and finds include Grandfather and prepend GratGrandfather. Does it actually 'go into' Grandfather where it includes GreatGrandfather, and only then executes the prepend GreatGrandfather line (and ignores it because it already exists in the ancestry)?

Truth be told, I doubt I will get much use out of it, but it won't hurt to know how exactly the modules 'chain' each other.

@edit - I've played around with it a bit more, and it doesn't seem that my intuition is right in either case. I've included a picture of the two ways I thought it could go, instruction after instruction, about creating the inheritance hierarchy - the one that appears to occur in the picture goes against the original example given, so neither #1 or #2 can be happening.

Modified example code (only GreatGrandfather changed)

module GreatGrandfather
  include Grandfather
end

module Grandfather
  include GreatGrandfather
end

module Father
  prepend GreatGrandfather
  include Grandfather
end

module Son
  include Father
end

Son.ancestors # => Son, GreatGrandfather, Father, Grandfather

enter image description here

in summary - I still have no idea how it happens

Upvotes: 2

Views: 893

Answers (1)

Alexey Kuznetsov
Alexey Kuznetsov

Reputation: 317

Module#prepend_feature

Ruby’s default implementation is to overlay 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.

But you already add GreatGrandfather to Father through Grandfather.

This way it would work as you expect:

module GreatGrandfather; end

module Grandfather
  include GreatGrandfather
end

module Father
  prepend GreatGrandfather
  include Grandfather
end

module Son
  include Father
end

p Son.ancestors # => [Son, GreatGrandfather, Father, Grandfather]

Update

1.You cant modify example like this:

module GreatGrandfather
  include Grandfather
end

module Grandfather
  include GreatGrandfather
end

cause when you define GreatGrandfather Grandfather is not defined.

2.This is, what happens when you add modules to another modules. Comments illustrate, what happens with module hierarchy in time:

module GreatGrandfather
  # GG
end

module Grandfather
  # GG
  # G
  include GreatGrandfather
  # GG
  # G -> GG
end

module Father
  # GG
  # G -> GG
  # F
  prepend GreatGrandfather
  # GG
  # G -> GG
  # GG -> F
  include Grandfather
  # Don't change the position of GG in Father hierarchy, cause it is already in ancestors
  # GG
  # G -> GG
  # GG -> F -> G
end

module Son
  # GG
  # G -> GG
  # GG -> F -> G
  # S
  include Father
  # GG
  # G -> GG
  # GG -> F -> G
  # S -> GG -> F -> G
end

Upvotes: 1

Related Questions