A deeper look into Ruby Modules

Image for post
Image for post

I recently completed the Launch School RB129 written assessment. After doing some revisions, and a lot of research online, I still felt like Ruby Modules were not extremely clear. After writing this article, I now feel like I have a good grasp on what a Module is, how to use them, and what their overall role is. I hope you feel the same after reading it!

What is a Ruby Module?

Why not just use a Class?

What is the role of a Module?

Other considerations

What is a Ruby Module?

A module is a way for a Ruby programmer to encapsulate logically similar classes, methods, constants, and even other modules. A module is included in other Ruby classes by using the include method, which can also be referred to as a mixin. Here is an example of a module:

module Towable
def tow
"I can tow something!"
end
end
class Truck
include Towable
end
truck = Truck.new
truck.tow # => "I can tow something!"

We define a module by using the module keyword and using a capital letter as the initial character. If a module had multiple words, then camel case syntax is preferred (ex: module TowTruck ). It is also a common naming convention to use an “able” suffix on the verb that the module is modeling.

Inside a module, we can define methods (class and instance methods) similar to how we would define methods inside of a class. In the example above we have defined an instance method tow within the Towable module. There is also a Truck class that has been defined, and we are using the include method to include the Towable module and all of the functionality that has been defined within it. Finally, we are initializing a local variable truck and assigning it to a new Truck object (or a new instance of the Truck class). Since we have access to the tow instance method, we invoke it on truck, and the program returns "I can tow something!".

Why not just use a Class?

Now I know what you’re thinking — why wouldn't I just create a class and inherit the instance methods that way. I’ll provide another example to show you why.

class HockeyPlayer
def slap_shot
"Top shelf! The goalie never had a chance."
end
end
class SoccerPlayer
def penalty_kick
"The ball was faster than the goalie!"
end
end
class Person
end

Here we have an example with three different classes — HockeyPlayer, SoccerPlayer, and Person. As a person who loves sports (not a Person object, a real person) I don’t want to have to choose between playing hockey or playing soccer. In Ruby, we are only able to inherit from one superclass (either the HockeyPlayer class or the SoccerPlayer class) which isn’t great. Let’s fix that by using modules!

module Hockeyable
def slap_shot
"Top shelf! The goalie never had a chance."
end
end
module Soccerable
def penalty_kick
"The ball was faster than the goalie!"
end
end
class Person
include Hockeyable
include Soccerable
end
nathen = Person.new
nathen.slap_shot # => "Top shelf! The goalie never had a chance."
nathen.penalty_kick # => "The ball was faster than the goalie!"

You can see that we are able to include multiple modules in the Person class, which means the Person object nathen has access to both slap_shot and penelty_kick instance methods. This is how Ruby addresses the inability to inherit from multiple classes — by way of multiple module mixins.

What is the role of a Module?

Now that you understand what a module is, and how you can use it, you are ready to understand what the role of a module is. When we define classes in a Ruby file, we are defining them in the global namespace. This is fine for simple programs, but as soon as you start to add more code and complexity, there is more room for clashing names of classes and methods. Take this example for instance:

# backend.rb
class User
def write_code
"You write awesome Ruby code!"
end
end
# more code in between#frontend.rb
class User
def write_code
"Finally, the box is centered."
end
end
nathen = User.new
nathen.write_code # => "Finally, the box is centered."

Well, that is confusing. What if we wanted to have a backend developer and a frontend developer? They both write code, but we don’t want one write_code instance method to overwrite the other. Once again, let’s bring a module to the rescue.

module BackEnd
class User
def write_code
"You write awesome Ruby code!"
end
end
end
module FrontEnd
class User
def write_code
"Finally, the box is centered."
end
end
end
nathen = BackEnd::User.new
nathen.write_code # => "You write awesome Ruby code!"

Here we have defined two modules that describe two different namespaces — BackEnd and FrontEnd namespaces if you will. Now we are not only able to be much more clear about whichUser object we are creating, but we are also able to identify much faster what we expect the program to output. You can see we are using the :: operator to instantiate the User class from within the BackEnd module, which is conveniently named the namespace resolution operator.

From this, we can tell thatnathen is assigned to the User object inside the BackEnd module, which is why invoking thewrite_code instance method on nathenreturns "You write awesome Ruby code!".

Note: The :: operator is not limited to only modules. It can be used on classes, modules, and constants.

This is a very simple example, but when we start to work with larger programs that include multiple files and many classes, you can see how class names and even instance methods can easily collide and be overwritten when you put all of them together. This is where the power of modules and creating those ‘safe places’ (namespaces) for you to define classes and methods come into play, making for a much nicer and less conflicted coding experience.

Other considerations

Now I just told you all of these awesome things about modules and how powerful they are, but when designing Object-Oriented programs there are a few things to consider before just putting modules everywhere.

Modules cannot be instantiated. If you are planning on creating instances that have access to all of the functionality within a module, just know that the module itself cannot be instantiated. You are able to instantiate a class within a module (like the developer example above), but the module itself cannot have instances of itself.

Modules modify the method lookup path. If we use the Person class from above I can demonstrate what I mean.

# modules omitted for brevity.. :(class Person
include Hockeyable
include Soccerable
end

In order to see the method lookup path, we need to use the .ancestors class method like this: Person.ancestors, which would return the following Array of objects:

[Person, Soccerable, Hockeyable, Object, Kernel, BasicObject]

We can see that our Soccerable and Hockeyable modules are included in the method lookup path (sometimes referred to as a hierarchy). One thing that is interesting is modules are ordered in the hierarchy from bottom to top, as opposed to top to bottom like they are included in the Person class. This is important when calling methods and knowing which classes and modules would be ‘looked in’ first. If Hockeyable and Soccerable each had an instance method with the same name, the one in Soccerable would be called first since it appears first in the method lookup path.

Modules can be included in multiple classes. I showed an example above on how multiple modules can be included in a single class, but it’s good to know that a single module can be added to multiple classes. This can be useful when you want functionality in one class, but you don’t want to create another class hierarchy, or if different classes do not inherit from the same superclass.

module Swimmable
def swim
"I can swim!"
end
end
class Pet
end
class Fish < Pet
include Swimmable
end
class Dog < Pet
include Swimmable
end
class Cat < Pet
end

We could define a swim method within the Pet class, but because cats can’t swim (or don’t like to), we wouldn’t want to do this. Here I have defined a Swimmable module and a swim method within it. Now I only had to define the swim method once and can include it in both the Fish and Dog class. This also makes sure only the Pet objects (or subclasses of the Pet class) that want to swim are able to swim.

This isn’t everything you could know about modules, but it is certainly a step in the right direction. I hope you enjoyed the article. Let me know if you had any questions or comments!

Launch Engineer @ Shopify

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store