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?
I mix its bag of tricks into B
This phrase and your question in general made me believe there is a little misunderstanding in concept of include
/extend
thing. I apologize in advance, because I don't fully understand the question.
For example you have such module:
module A
def a
puts "a"
end
def self.b
puts "b"
end
end
As you see there are 2 types of methods:
Here is the easiest way to show that they actually differ:
A.singleton_methods
=> [:b]
A.instance_methods
=> [:a]
A.a
NoMethodError: undefined method `a' for A:Module
A.b
b
=> nil
If you do include A
simplistically you are adding its instance methods to the current module instance methods. When you do extend A
simplistically you are adding its instance methods to the current module singleton methods.
module B
include A
end
module C
extend A
end
B.instance_methods
=> [:a]
B.singleton_methods
=> []
C.instance_methods
=> []
C.singleton_methods
=> [:a]
One more thing to say is that you could extend self
but not include self
as that doesn't make any sense and also will raise an exception.
module D
extend self
def a
puts "a"
end
def self.b
puts "b"
end
end
D.singleton_methods
=> [:b, :a]
D.instance_methods
=> [:a]
D.a
a #no error there because we have such singleton method
=> nil
I guess these things could help you. There are a lot of questions about extend
/include
on StackOverflow you may check (example).