16

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?

ivan
  • 6,032
  • 9
  • 42
  • 65
  • Is there any way to simplify your question? Even if you were to put it in a gist with the different scenarios debugged out. – onebree Nov 23 '15 at 21:54
  • I think you may be confusing the scope change of self when you are extending A inside of B, and then expecting that C will have A. You change scope. There is also #prepend that can be called. To see how these scopes change, call the $ancestors method on your class. – R.J. Robinson Nov 24 '15 at 14:52
  • 1
    Sounds like you want to call both mom and daughter by same name :P – Vasu Adari Nov 27 '15 at 07:46

2 Answers2

9

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:

  • singleton_methods
  • instance_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).

Community
  • 1
  • 1
Maxim Pontyushenko
  • 2,983
  • 2
  • 25
  • 36
  • 2
    Good answer! One bit got accidentally switched around, I think. One of the statements above should read: "When you do `extend A` simplistically you are adding its **instance** methods to the current module **singleton** methods." (the *"instance"* and "*singleton*" are swapped in the answer) – ivan Jan 20 '16 at 04:45
0

The following seems to work at least for A to B to C

module A
  def learned_from_A
    true
  end
end

module B
  prepend A
end

B.learned_from_A  # true

module C
  extend B
end

C.learned_from_A  # true
johnny
  • 327
  • 3
  • 10