In this article I would like to mention the basic way to minimize code duplication in the application, you will often hear about DRY – Don’t Repeat Yourself.
Inheritance
Ruby is an object- oriented programming language ( OOP ) so it has inheritance , and it is used for classes. In ruby, just declare simply by the character <
, and the order is a child <
father. See the following simple example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <span class="token keyword">class</span> <span class="token class-name">Animal</span> attr_accessor <span class="token symbol">:color</span> <span class="token keyword">def</span> speak <span class="token string">"Hello!"</span> <span class="token keyword">end</span> <span class="token keyword">def</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> is_human <span class="token operator">?</span> <span class="token keyword">false</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Dog</span> <span class="token operator"><</span> <span class="token constant">Animal</span> attr_accessor <span class="token symbol">:name</span> <span class="token keyword">def</span> initialize name <span class="token variable">@name</span> <span class="token operator">=</span> name <span class="token keyword">end</span> <span class="token keyword">def</span> speak <span class="token string">" <span class="token interpolation"><span class="token delimiter tag">#{</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> name <span class="token delimiter tag">}</span></span> says arf!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Cat</span> <span class="token operator"><</span> <span class="token constant">Animal</span> <span class="token keyword">end</span> |
In the above declaration 3 classes including Animal
, and Dog
, Cat
are 2 direct subclasses of Animal
. Then we have the following:
1 2 3 4 5 6 7 8 9 | 2.5.1 :001 > Animal.class => Class 2.5.1 :002 > Dog.class => Class 2.5.1 :003 > Dog.superclass => Animal 2.5.1 :004 > Cat.superclass => Animal |
Dog
and Cat
superclass
are both Animal
1 2 3 4 5 6 7 8 9 | <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">005</span> <span class="token operator">></span> <span class="token constant">Cat</span> <span class="token punctuation">.</span> is_human <span class="token operator">?</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token keyword">false</span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">010</span> <span class="token operator">></span> cat <span class="token operator">=</span> <span class="token constant">Cat</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token comment">#<Cat:0x00000000026644a8> </span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">011</span> <span class="token operator">></span> cat <span class="token punctuation">.</span> color <span class="token operator">=</span> <span class="token string">"grey"</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"grey"</span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">012</span> <span class="token operator">></span> cat <span class="token operator">=</span> <span class="token operator">></span> <span class="token comment">#<Cat:0x00000000026644a8 @color="grey"> </span> |
Cat
doesn’t need to define anything but still executes the above statement because Cat
inherits all the class method, instance method and its parent variable is Animal
class.
1 2 3 4 5 6 7 8 9 10 11 | <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">005</span> <span class="token operator">></span> <span class="token constant">Dog</span> <span class="token punctuation">.</span> is_human <span class="token operator">?</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token keyword">false</span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">006</span> <span class="token operator">></span> dog <span class="token operator">=</span> <span class="token constant">Dog</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token string">"Reck"</span> <span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token comment">#<Dog:0x000000000254f8d8 @name="Reck"> </span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">007</span> <span class="token operator">></span> dog <span class="token punctuation">.</span> color <span class="token operator">=</span> <span class="token string">"brown"</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"brown"</span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">008</span> <span class="token operator">></span> dog <span class="token operator">=</span> <span class="token operator">></span> <span class="token comment">#<Dog:0x000000000254f8d8 @name="Reck", @color="brown"> </span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">032</span> <span class="token operator">></span> cat <span class="token punctuation">.</span> name <span class="token operator">=</span> <span class="token string">"Mimi"</span> <span class="token constant">NoMethodError</span> <span class="token punctuation">(</span> undefined method `name <span class="token operator">=</span> ' <span class="token keyword">for</span> <span class="token comment">#<Cat:0x00000000026644a8 @color="grey">)</span> |
Dog
also inherits methods and variables from Animal
, in addition to the color
variable from the parent, it also defines its own variable name
. So unlike Cat
, Dog
has more variables for its object.
1 2 3 4 5 | <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">009</span> <span class="token operator">></span> dog <span class="token punctuation">.</span> speak <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Reck says arf!"</span> <span class="token number">2.5</span> <span class="token number">.1</span> <span class="token punctuation">:</span> <span class="token number">013</span> <span class="token operator">></span> cat <span class="token punctuation">.</span> speak <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">"Hello!"</span> |
In Dog
and Animal
, there is a speak
method, which means that Dog
is overwriting the parent’s own method that it inherits, while Cat
does not override this method. When calling speak
as above, the dog
will put content inside the speak
method of the Dog
class, while for cat
it will also take the speak
method of the Animal
class.
Overwriting the father’s method in the inheritance tree is polymorphism
– polymorphism
– one of the four important properties of OOP .
super
With its inheritance, Ruby provides you a great method that is super
. When you call super
at a method in the current class, it will look up the parent classes in the inheritance tree to find the method with the same name as the other method to execute it. See an example to make it easier to imagine:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">Animal</span> <span class="token keyword">def</span> speak <span class="token string">"Hello!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Dog</span> <span class="token operator"><</span> <span class="token constant">Animal</span> <span class="token keyword">def</span> speak <span class="token keyword">super</span> <span class="token operator">+</span> <span class="token string">" from GoodDog class"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 | 2.5.1 :001 > dog = Dog.new => #<Dog:0x0000000001e88950> 2.5.1 :002 > dog.speak => "Hello! from Dog class" |
We see, when calling super
in the speak
method of the Dog
class, it finds and implements the speak
method in the Animal
class as well.
A familiar way to use super
is at initialization with the initialize
method as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">class</span> <span class="token class-name">Animal</span> attr_accessor <span class="token symbol">:name</span> <span class="token keyword">def</span> <span class="token function">initialize</span> <span class="token punctuation">(</span> name <span class="token punctuation">)</span> <span class="token variable">@name</span> <span class="token operator">=</span> name <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Dog</span> <span class="token operator"><</span> <span class="token constant">Animal</span> attr_accessor <span class="token symbol">:color</span> <span class="token keyword">def</span> <span class="token function">initialize</span> <span class="token punctuation">(</span> name <span class="token punctuation">,</span> color <span class="token punctuation">)</span> <span class="token keyword">super</span> <span class="token punctuation">(</span> name <span class="token punctuation">)</span> <span class="token variable">@color</span> <span class="token operator">=</span> color <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 | 2.5.1 :001 > Dog.new("Reck", "brown") => #<Dog:0x0000000002a5c718 @name="Reck", @color="brown"> 2.5.1 :002 > dog.name => "Reck" 2.5.1 :003 > dog.color => "brown" |
As you can see above, the Dog
class has no initialization for the name
attribute, and instead it calls super
to find the initialize
method in the Animal
class. And so after the initialization is complete, the dog
has both name
and color
.
Mixing in the module.
Another common way to DRY code is to use module
. Recalling the definition of the module a bit, in Ruby, the module is used to group methods, variables, constants together (when grouping classes, the module
keyword now acts as a namespace). We mix the module into the class with the keyword include
, prepend
or extend
.
For example with a simple inheritance tree as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token keyword">module</span> <span class="token constant">Swimmable</span> <span class="token keyword">def</span> swim <span class="token string">"I'm swimming!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Animal</span> <span class="token punctuation">;</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Fish</span> <span class="token operator"><</span> <span class="token constant">Animal</span> include <span class="token constant">Swimmable</span> <span class="token comment"># mixing in Swimmable module</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Mammal</span> <span class="token operator"><</span> <span class="token constant">Animal</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Cat</span> <span class="token operator"><</span> <span class="token constant">Mammal</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Dog</span> <span class="token operator"><</span> <span class="token constant">Mammal</span> include <span class="token constant">Swimmable</span> <span class="token comment"># mixing in Swimmable module</span> <span class="token keyword">end</span> |
Test run we get the results:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 2.5.1 :022 > reck = Dog.new => #<Dog:0x0000000000cf3a28> 2.5.1 :023 > nemo = Fish.new => #<Fish:0x0000000000cef798> 2.5.1 :024 > paws = Cat.new => #<Cat:0x0000000000ce37b8> 2.5.1 :026 > reck.swim => "I'm swimming!" 2.5.1 :027 > nemo.swim => "I'm swimming!" 2.5.1 :028 > paws.swim NoMethodError (undefined method `swim' for #<Cat:0x0000000000ce37b8>) |
We see reck
and nemo
can call the swim
method and not paws
, because only the Dog
and Fish
classes have the Swimmable
module mixin inside it.
Ancestors method
Above are 2 ways to DRY code. In this section, I would like to introduce you a little trick so that when you call a method from a class or from a specific instance of that class, the ancestors
method.
Suppose you have a inherited mixin pattern as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">module</span> <span class="token constant">Swimmable</span> <span class="token keyword">def</span> swim <span class="token string">"I'm swimming!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Animal</span> <span class="token keyword">def</span> swim <span class="token string">"Animal swimmable"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Fish</span> <span class="token operator"><</span> <span class="token constant">Animal</span> include <span class="token constant">Swimmable</span> <span class="token keyword">def</span> swim <span class="token string">"Fish swimmable"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 | 2.5.1 :021 > Fish.new.swim => "Fish swimmable" |
We see when calling the swim
method from the instance of the Fish
class, it will call the swim
method in the same class. Although both Animal
and Swimmable
also have this method, it is the override mentioned above. But what it is essentially, call the ancestors
method to see what it returns you.
1 2 3 | 2.5.1 :022 > Fish.ancestors => [Fish, Swimmable, Animal, Object, Kernel, BasicObject] |
The ancestors
method will return a list of all ancestor classes in the inheritance tree and include modules that are mixin inside the tree’s classes ( Kernel
is a mixin module in the Object
class). And every time a method is called from the class or instance of that class, Ruby will go in turn in order of each class and module according to the results of the ancestors
method to search for the called method. As in the above example, the method will be searched in the order Fish
-> Swimmable
-> Animal
-> Object
-> Kernel
-> BasicObject
. Where the swim
method is found, we will jump into the implementation and return the result, and the result is the swim
method of the Fish
.
As a final example, suppose I created a new Pree
module and mixin it in the Fish
class with the prepend
keyword
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <span class="token keyword">module</span> <span class="token constant">Pree</span> <span class="token keyword">def</span> swim <span class="token string">"Prepend swimming!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">module</span> <span class="token constant">Swimmable</span> <span class="token keyword">def</span> swim <span class="token string">"I'm swimming!"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Animal</span> <span class="token keyword">def</span> swim <span class="token string">"Animal swimmable"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Fish</span> <span class="token operator"><</span> <span class="token constant">Animal</span> prepend <span class="token constant">Pree</span> include <span class="token constant">Swimmable</span> <span class="token keyword">def</span> swim <span class="token string">"Fish swimmable"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 | 2.5.1 :027 > Fish.ancestors => [Pree, Fish, Swimmable, Animal, Object, Kernel, BasicObject] |
For this example, the method will be searched in the order Pree
-> Fish
-> Swimmable
-> Animal
-> Object
-> Kernel
-> BasicObject
. As you may have noticed, prepend
will insert the module first, while include
will insert the module immediately after that class.
Inheritance vs Modules
In the final section of this article, I would like to give some comparisons between inheritance and module
- Ruby does not provide multiple inheritance in the form of a child with multiple fathers, but can mixin freely many modules in the class.
- Cannot create an instance of module, module only uses group of methods, variables, constants and namespace
- For
is-a
relations, use inheritance, ex:Dog
is-a
Animal
. Forhas-a
relation, use mixin, ex:Dog
has-a
swim
ability
Refer
https://launchschool.com/books/oo_ruby/read/inheritance#classinheritance https://www.oreilly.com/learning/ruby-cookbook-modules-and-namespaces
Thank you for following the article.