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:
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
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
class Horse < Mammal include Equidae def whinny "Whinny!" end end
We can now call all
Equidae methods on instances of
> 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
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
String mixes in
Array mixes in
We’ve already learned a few
Kernel methods such as
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.
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
Modules have other important uses as well. Remember Ruby scope gates?
module is a scope gate just like the
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.
Mixin: Code added to a class from a module.
Module: A container for storing methods.
Namespace: A container for encapsulating code.
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