Ruby Modules Part 2
Modules and Object Hierarchy
Let’s take a more in depth look at the inner workings of Modules in Ruby. As we learned in the previous post on Modules, including a module in a class exposes the module’s methods as instance methods of the class. This is because the Module gets inserted into the Class’s ancestry chain.
Ruby’s ancestry chain, or object hierarchy, is worth exploring. Though a particular class can only inherit from one superclass, it is likely that the superclass itself inherits from another class, creating an ancestry chain where methods from the superclasses cascade down to the children. The ancestors
and superclass
methods help illustrate this:
Calling .ancestors
on a class or module will show its ancestry chain, including all modules included by the class and its ancestors. In this case Movie
includes the Streamable
module. Movie
inherits from the Media
class, which inherits from Object
. Object
includes the Kernel
module and inherits from BasicObject
.
We do not get Kernel
returned from calls to superclass
because Kernel
is a module. We can use the included_modules
method to see only the modules in the ancestry chain:
Class Methods via Modules
Cool! Ok, so what if we want to use a module to expose class methods rather than instance methods to the classes that include it? We can do this using extend
when we include the module:
In the code above we defined a module, Searchable
, to be used in any class that has a library of items that we want to search. It contains the method starts_with_letter
, intended to be a class method, that takes a letter and returns all the items in the collection whose name begin with that letter. We use extend
when including the module to tell Ruby that we want to use the methods in Searchable
as class methods. When we attempt to call starts_with_letter
on an instance of Movie
we will get a NoMethodError
.
Class Methods AND Inheritance Methods
Let’s not limit ourselves! We don’t have to choose between instance and class methods when writing a module. We can use both. Here is how we do it:
In the above snippet, we wrapped Searchable
’s class methods in a module called ClassMethods
. We then both included the module using include Searchable
and extended the class methods using extend Searchable::ClassMethods
. This does the trick but there is a cleaner way to achieve the same result. We can use one of Ruby’s built-in method hooks. Method hooks are methods that are automatically called when a particular event occurs. Whenever a module is included in a class, Ruby calls the included
method hook. We can use this hook to automatically extend the class methods within a module. Check it:
Now whenever the module is included in a class, Searchable.included
is called (with base
being a reference to the class including the module) and the class methods are automatically extended. It is worth noting that the name ClassMethods
for the module in which the class methods are enclosed is just a convention. You can call this module whatever you want and then tell included
to extend it: