Here is a complete story explaining the necessary metaprogramming concepts needed to understand why module inclusion works the same way as in Ruby.
What happens when the module is turned on?
Inclusion of a module in a class adds a module to the ancestors of the class. You can look at the ancestors of any class or module by calling its ancestors method:
module M def foo; "foo"; end end class C include M def bar; "bar"; end end C.ancestors
When you call a method in an instance of C , Ruby will look at each element of this list of ancestors to find the instance method with the name provided. Since we included M in C , M now an ancestor of C , so when we call foo on an instance of C , Ruby will find this method in M :
C.new.foo
Note that the inclusion does not copy any instance or class methods to the class - it simply adds a βnoteβ to the class, that it should also look for instance methods in the included module.
What about class methods in our module?
Since inclusion only changes the way methods of the instance are sent, including the module to the class , only makes its instance methods available to this class. Class methods and other declarations in the module are not automatically copied to the class:
module M def instance_method "foo" end def self.class_method "bar" end end class C include M end M.class_method
How does Ruby implement class methods?
In Ruby, classes and modules are simple objects β they are instances of the Class and Module class. This means that you can dynamically create new classes, assign them to variables, etc.:
klass = Class.new do def foo "foo" end end
Also in Ruby, you have the option of defining what are called singleton methods for objects. These methods are added as new instance methods to the special hidden singleton class of the object:
obj = Object.new # define singleton method def obj.foo "foo" end # here is our singleton method, on the singleton class of `obj`: obj.singleton_class.instance_methods(false) #=> [:foo]
But aren't classes and modules just simple objects? In fact they are! Does this mean that they can have solid methods? Yes! This is how class methods are born:
class Abc end # define singleton method def Abc.foo "foo" end Abc.singleton_class.instance_methods(false) #=> [:foo]
Or a more common way to define a class method is to use self in the class definition block, which refers to the created class object:
class Abc def self.foo "foo" end end Abc.singleton_class.instance_methods(false) #=> [:foo]
How to include class methods in a module?
As we just established, class methods are really just instance methods for a singleton class of a class object. Does this mean that we can just include the module in a singleton class to add a bunch of class methods? Yes it is!
module M def new_instance_method; "hi"; end module ClassMethods def new_class_method; "hello"; end end end class HostKlass include M self.singleton_class.include M::ClassMethods end HostKlass.new_class_method
This line self.singleton_class.include M::ClassMethods does not look very good, so Ruby added Object#extend , which does the same - that is, it includes the module in a singleton object class:
class HostKlass include M extend M::ClassMethods end HostKlass.singleton_class.included_modules
Moving an extend call to a module
This previous example is not well-structured code for two reasons:
- Now we need to call both
include and extend in the HostClass definition to enable our module correctly. This can be very cumbersome if you have to enable many of these modules. HostClass directly references M::ClassMethods , which is a detail of an implementation of the M module that HostClass should not know or care.
So, how about this: when we call include on the first line, we somehow notify the module that it was turned on, and also give it our class object so that it can call extend . So this is a module job to add class methods if it wants to.
This is exactly what the special self.included method is for . Ruby automatically calls this method whenever a module is included in another class (or module) and passes the first argument to the host class object:
module M def new_instance_method; "hi"; end def self.included(base) # `base` is `HostClass` in our case base.extend ClassMethods end module ClassMethods def new_class_method; "hello"; end end end class HostKlass include M def self.existing_class_method; "cool"; end end HostKlass.singleton_class.included_modules #=> [M::ClassMethods, Kernel] # ^ still there!
Of course, adding class methods is not the only thing we can do in self.included . We have a class object, so we can call any other (class) method on it:
def self.included(base)