I'm trying to better understand how modules extend and include each other.
Say I have module A:
module A
def learned_from_A
true
end
end
A.instance_methods # [:learned_from_A]
I mix its bag of tricks into B:
module B
extend A
end
B.learned_from_A # true
I naively attempt to give C everything B has:
module C
extend B
end
C.learned_from_A # NoMethodError
I think I've wrapped my head around this. When B extends A, copies of A's instance methods are bound to B via B's singleton class:
B.singleton_methods # [:learned_from_A]
While :learned_from_A is callable on B, it's not one of B's instance methods, so when C extends B, :learned_from_A is not copied to C.
If B had instead included A, copies of A's instance methods would've been included among B's own instance methods.
module B
include A
end
B.instance_methods # [:learned_from_A]
Then, C could extend B, and all of B's instance methods (including :learned_from_A) would be copied and bound to C.
module C
extend B
end
C.singleton_methods # [:learned_from_A]
To make :learned_from_A callable on both B and C, B could extend and include A.
module B
include A
extend A
end
B.instance_methods # [:learned_from_A]
B.singleton_methods # [:learned_from_A]
module C
extend B
end
C.instance_methods # []
C.singleton_methods # [:learned_from_A]
More realistically, if I want A's methods to be callable on B, and for B to define another method of its own, and be able to mix the whole repertoire into C, I can't do this:
module B
extend A
include A
def self.buzz
true
end
end
module C
extend B
end
B can only share its instance methods, not its singleton methods. So to make a method both callable on B and shareable to other objects, it must be defined as an instance method and extended into B itself:
module B
extend A
include A
extend self
def buzz
true
end
end
module C
extend B
end
There was a fair amount of trial and error in putting this all together. Is it an accurate way of viewing what's going on?