Search code examples
rubymoduleextend

Ruby: modules extending/including modules


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?


Solution

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