Lesson Tuesday

Single inheritance doesn’t cover all our coding needs. If we want a class to have methods from outside the inheritance chain, we need to use mixins. A mixin literally "mixes in" code from a module. A module is really just a container to hold methods. Modules don't have an initialize method and they aren't used to instantiate objects.

Once again, you are not required to use either mixins or modules for your in-class projects or on your independent project. However, these concepts are an important part of Ruby so you should know about them now.

Let’s expand our example from the previous lesson. We already have the following classes: Mammal, Cat and Dog. We’re ready to add horses but we may also want to add other Equidae (members of the Horse family) such as zebras and donkeys. Equidae should have their own set of methods such as gallop() and trot(). While zebras and horses should have these methods, cats and dogs should not. While we could use single inheritance (Horse inherits from Equidae inherits from Mammal), we can also use modules. Here’s how:

module Equidae
  def gallop
    "Clippety clop clippety clop!"
  end

  def trot
    "Clop clop clop clop."
  end
end

In order to use our Equidae module in our Horse class, we need to include it:

class Horse < Mammal
  include Equidae

  def whinny
    "Whinny!"
  end
end

We can now call all Equidae methods on instances of Horse:

> horse.gallop()
=> "Clippety clop clippety clop!"
> horse.trot()
=> "Clop clop clop clop."

We are now essentially mimicking multiple inheritance; in addition to inheriting from all the classes in Horse’s inheritance chain (including Object and BasicObject), we can mix in methods from as many modules as we like.

In fact, we can use Ruby’s ancestors() method to see both the inheritance chain and the modules mixed into a class:

Horse.ancestors()
=> [Horse, Equidae, Mammal, Object, Kernel, BasicObject]
String.ancestors()
=> [String, Comparable, Object, Kernel, BasicObject]
Array.ancestors()
=> [Array, Enumerable, Object, Kernel, BasicObject]

Horse mixes in Equidae and Kernel. String mixes in Comparable and Kernel. And Array mixes in Enumerable and Kernel.

We’ve already learned a few Kernel methods such as puts and chomp. In fact, Kernel is a module included in Object, which illustrates an important point: if a class’s superclass has a module mixed in, then the class itself will also be able to use the methods in that module, even though the module is included in the superclass, not the class.

These examples show just how common modules are in the Ruby language and how powerful they can be. For instance, the Comparable module, which is also mixed into the Integer class, allows developers to decide how two values should be compared. For instance, should "a" be greater than "bb" because it’s alphabetical? Or should "bb" be greater than "a" because it has a greater length? The Comparable module gives us the flexibility to decide depending on the needs of our application.

The Enumerable module is another excellent example. This module provides some of the most powerful methods in Ruby for iterating over collections, including map(). We’ll be going over map() and other Enumerable methods in upcoming lessons.

While the example in this lesson could easily be solved using the single inheritance, it’s very common to have a set of methods that can be mixed in much in the way that Ruby mixes in Enumerable and Comparable.

Modules have other important uses as well. Remember Ruby scope gates? module is a scope gate just like the def and class keywords. In other words, modules can be a very effective way of encapsulating and namespacing code. A namespace in Ruby is really just a container for objects. (Namespacing is a general computer science term for this kind of container, but not all programming languages are object-oriented, so not all namespaces will contain objects.) We'll discuss namespacing further in the next course section.

If you have extra time after finishing a project for the day's classwork, try refactoring it to use modules. Once again, you won’t be expected to add modules to this section's independent project, but modules are an essential part of Ruby so you should be familiar with them. You will see them regularly both in the Rails framework and elsewhere.

Terminology


Mixin: Code added to a class from a module.

Module: A container for storing methods.

Namespace: A container for encapsulating code.

Example


To create a module:

module ThisIsAModule
  # Instance methods are stored in a module.
end

To include a module:

class ThisIsAClass
  include ThisIsAModule

end

Lesson 13 of 22
Last updated August 7, 2022