Welcome back dear friends, dear readers, coders, and enthusiasts!
Welcome to our next article where we present the preciousness of our beloved Ruby PL. This time we are going to look through the “Modules” topic in Ruby. We will also strengthen our learning by showing some examples of how to use Modules in Ruby.
....................................................
What are the Modules?
In short, a Module is a way to gather classes, methods, and constants under one roof. To expand the topic a bit wider, this gives us two widely used methodologies in programming.
Collection — otherwise named as a ‘namespace’, gives us an opportunity to gather classes, methods, and constants under a single roof.
Mixin — a mechanism to include a ready module into another class and use its content (methods, values, constants) as its own. Thus we can expand the possibilities of that class.
After mentioning its advantages in general, let’s dive deeper into it and look at the above-mentioned items in detail. We will also provide explanations with code examples where possible.
....................................................
1. Collection
Imagine such a situation. You decided to collect together operators, functions, and all the related data into one place. Why should we not make it a module?
module Trig
PI = 3.1416 # A Constant
Trig.sin(x)
#...
end
Trig.cos(x)
#...
end
...
...
end
We can use the above-defined methods and constants somewhere else. Let’s use this module to perform trigonometric calculations. Here is an example:
radius = 6
# Here we call a constant named 'PI' from module 'Trig'
area = Trig.PI*radius**2 # PI*R^2 formula
# Let's calculate sine and cosine of the angle.
# Here we call 'sin' and 'cos' methods from
# 'Trig' module.
angle = 45
sine = Trig.sin(angle)
cosine = Trig.cos(angle)
If these examples are a bit easy for you, we can take a look at more complex ones. Now, let’s take a look at a module that comprises several classes in it.
You decided to develop a ‘Customer’ class. It has a few properties and methods. Depending on the context, a customer can act differently. So let’s write a class that has a bit different methods and properties depending on the type of customer in a particular situation, and place them in different modules.
module Bank
class Customer # A bank customer.
attr_accessor :balance, first_name, last_name
def put_cash(sum)
puts "#{sum} amount of cash was put."
end
def withdraw(sum)
puts "#{sum} amount was withdrawn."
end
end
end
module Shop
class Customer # A shop customer
attr_accessor :products
def pay_for_products
products.each do |product|
puts "#{product} bought."
end
end
...
...
end
end
I hope you paid attention to how we separated the issues and placed two different customers in two different modules. Both of the modules have ‘Customer’ class, but they behave differently. In other words, they have different methods and properties. If we have defined both classes in the same namespace, we would have had a conflict. And that would be a name conflict at least, not to mention the others. Ruby would not have allowed us to write the same-named class within the same namespace. So we just place them in different modules and no conflicts comes up.
Now we can easily call and use the ‘Customer’ class and its methods in the following examples:
# Example - 1
bank_customer = Bank::Customer.new
bank_customer.balance = 25
bank_customer.first_name = "John"
bank_customer.last_name = "Brown"
bank_customer.withdraw(50)
# Example - 2
shop_customer = Dukan::Customer.new
shop_customer.products = ["apple", "fig", "bread", "milk"]
shop_customer.pay_for_products
As you see in the examples above, by placing classes, modules, and constants into different modules we can avoid name conflicts and have them being used within the right context.
Now let’s take a look at how we can use them as Mixins.
...................................................
2. Mixins
Example A : Class relationships
There is a bit complex relationship matter called ‘inheritance’ in the programming world. It is a case when a class is a child/subclass of another one, that we call as ‘parent’. This way a child class possesses the features and methods of a parent class. Here is an example of this:
# Define a class for animals.
class Animal
attr_accessor :name, :category
def make_noise
puts 'Making noise...'
end
def move
puts 'Moving...'
end
end
The next classes to be defined are somewhat part of, or subpart of an ‘Animal’ class. And they will possess the features and methods of an ‘Animal’ class. So, let’s define classes ‘Fish’ and ‘Bird’ and make them as child classes of an ‘Animal’ class. ‘Ruby’ PL makes it this way:
# 'Fish' class
class Fish < Animal
...
end
# 'Bird' class
class Bird < Animal
...
end
Now we can call methods of an ‘Animal’ class and use its properties from within ‘Fish’ and ‘Bird’ classes.
a_fish = Fish.new
a_fish.name = 'Ariel'
a_fish.make_noise # 'Making noise...'
a_bird = Bird.new
a_bird.name = 'Dove'
a_bird.move # 'Moving...'
Example B : Mixins
Now, getting inspired by the examples above, let’s write something a bit different. A film, an advertisement, a clip are all Videos. We can ‘play’ them and ‘pause’ them all.
Besides that, we can download them onto our PC or mobile device. Suppose we can download only video, only audio, or both at the same time. We have ‘Download’ module for this.
So let’s define ‘Video’ and ‘Download’ modules for this purpose.
# 'Video' module
module Video
def play
puts 'Playing ...'
end
def pause
puts 'Paused...'
end
end
# 'Download' module
module Download
def download_video
puts 'Downloading only video...'
end
def download_audio
puts 'Downloading only audio...'
end
def download_both
puts 'Download audio & video...'
end
end
Now let’s define other 3 classes and make methods of ‘Video’ and ‘Download’ modules available to them.
# 'Film' class
class Film
attr_accessor :title
include Video
include Download
end
# 'Advertisement' class
class Advertisement
include Video
include Download
end
# 'Clip' class
class Clip
include Video
include Download
end
Now we can use features of ‘Video’ and ‘Download’ modules after making them available within those lately defined classes. Here is an example:
# 'Film' class
silence = Film.new
silence.title= 'The Silence of Lambs'
silence.play # 'Playing ...'
silence.download_both # Download audio and video
# 'Advertisement' class
cola_ad = Advertisement.new
cola_ad.play # 'Playing ...'
cola_ad.download_audio # Download only audio
# 'Clip' class
drake_clip = Clip.new
drake_clip.play # 'Playing ...'
drake_clip.download_video # Download only video
So we defined ‘Video’ and ‘Download’ as modules, and not classes. Lately, we made their features available to the other three classes. We used ‘include’ keyword for that.
Differences between Example A and Example B
The relationship between a Parent and a Child class is very important in programming. We implement it with a mechanism called ‘inheritance’ and avoid writing excess code. Thus we support DRY (Don’t Repeat Yourself).
Both ‘A’ and ‘B’ examples are doing almost the same thing but there are few differences between them. A Ruby class can not inherit from multiple classes at the same time. In other words, a class can not have multiple parent classes at the same time. So we could not inherit from ‘Video’ and ‘Download’ classes at the same time. For this reason, we implemented ‘Video’ and ‘Download’ as modules and used them as Mixins in ‘Film’, ‘Advertisement’, and ‘Clip’ classes.
.................................................
Conclusion
And now, we introduced our beloved Ruby PL closer and learned how to implement Modules in it. Besides that, we learned in which cases we should implement something as a Class or as a Module.
One of the famous modules in Ruby PL are ‘Enumerable’ and ‘Comparable’ modules. By including these modules into your custom classes, you add comparison and numerating functionality to your own classes. This way you can compare two instances of your own class and iterate through its elements if it is a type of collection.
Hope to meet you soon in the next article, dear friends.
Stay healthy, wealthy, and wise!
Top comments (0)