Inheritance in Ruby: The Ancestor Chain
In the same way Eurasier puppies inherit attributes and behaviors from their parents, or heirs receive assets from the deceased, Ruby classes can inherit methods and behaviors from their superclasses. This functionality is fundamental to object-oriented programming, allowing for the creation of increasingly specific subclasses by leveraging method reusability. A child (subclass) inherits all the methods of its parent (superclass), and can increase in specificity depending on the programmer’s needs. These capabilities are illustrated in the following example:
On line 7 in the example (above), the subclass Bulldog
inherits the methods from its superclass Dog
using the <
key. The Bulldog
class inherits the #speak
method and becomes increasingly specific by introducing a new #sleep
method unique to itself. When the Bulldog
instance of baby
is created, it’s clear that she has the ability to both #speak
and #sleep
.
Now imagine a scenario where a new subclass of Dog
is required, but this specific type of dog’s speaking would be better described as “Yip”
instead of “Woof”
.
In Example 2 (above), both the Schitzu
subclass and the Dog
superclass contain a method with the same name. When an instance of Schitzu
is created, and #speak
is called on it, the behavior inherited from the Dog
class is overridden. If for some reason the programmer desires the #speak
behavior from the superclass, and doesn’t want it to be overridden by Schitzu
, the super keyword can be utilized.
The super keyword is used to call a method of the same name in the superclass. matilda
is now expressing the #speak
behavior of its superclass Dog
, even though it has a method of the same name.
The Ancestors Chain
In the next section, different methods of inheriting behavior from classes and modules will be explored. .ancestors
is a class method in Ruby that returns an array of classes and modules commonly known as the ancestors chain. The order of the elements in this array are listed in a hierarchy of increasing parent rank.
puts String.ancestors# => [String, Comparable, Object, Kernel, BasicObject]
When the .ancestors
method is called on the String
class, its respective ancestors chain is returned. From the top down BasicObject
is the parent class of all Ruby classes, Kernel
is its child module, Object
is the child class of Kernel
, Comparable
is the child module of Object
and the superclass of String
.
When a method is called, a lot happens under the hood. Through the process of method lookup, the chain is referenced, and the method is executed using the instructions defined in the first class or module that appears in the ancestors chain containing that method name.
The above example utilizes the include keyword. This keyword is another way to express inheritance in Ruby. The included object is placed directly after the object that includes it in the ancestors chain. The ancestor chain in the above example is expressed as:
[AstroPhysics, Physics, Science, Object, Kernel, BasicObject]
AstroPhysics
, the object that .ancestors
was called on is the first in the chain, followed by its parent Physics
, followed by its parent Science
, etc. Typically, the object that .ancestors
was called on, is first in the chain. However, there is one keyword that makes an exception: prepend.
Note that in the above example, Harvard.ancestors
was called, but the first module in the chain was LawSchool
. The prepend keyword places LawSchool
in front of Harvard in the ancestors chain.
puts Harvard.ancestors# => [LawSchool, Harvard, University]
Imagine a situation where all of the class/module objects contain a method named scale
. The ancestors chain can be a useful tool to use when trying to figure out what instructions for scale
to follow. When viewing the ancestors chain, the object hierarchy in Ruby can be clearly visualized. Example 6 illustrates this point.
The Drum
class includes the Instrument
module containing the scale
method. However, because Drum
also includes a scale
method, the ancestors chain tells the programmer that the scale
method in the Drum
class will take priority over all of the other objects in the chain.
Resources: