Modules

Learn about modules in Ruby and how they relate with classes.

It now makes sense to introduce another language feature in Ruby: modules.

Modules are somewhat similar to classes in Ruby. They hold methods just like classes do. However, modules can’t be instantiated. It’s not possible to create objects from a module. Modules, unlike classes, don’t have a new method.

What are modules useful for?

We can share methods between classes with modules. Modules can be included into classes, and this makes their methods available in the class, just as if we’d copied and pasted these methods over to the class definition.

This is useful if we have methods that we want to reuse in certain classes but also want to keep in a central place so that we don’t have to repeat them everywhere.

A toy example

Let’s have a look at this pretty contrived code:

Press + to interact
module Cream
def cream?
true
end
end
class Cookie
include Cream
end
cookie = Cookie.new
p cookie.cream?

This minimal code example is rather strange. That said, it’s a perfect quick example.

This code outputs true. Why is that?

The cream? method is defined on the Creammodule, and all it does is always return the valuetrue. This module is included in the Cookieclass. So, if we now instantiate a cookie, we can call the methodcream?on it, and it returns the valuetrue`.

Remember: We use the module keyword to create a module, and we use the include keyword to include it in another class.

Let’s move on and use this for our Person class, which will hopefully then make more sense.

Another example

Let’s assume that our application has other classes that need to encrypt things. We want to keep the exact way of how we encrypt things, the implementation, in one single place.

Why would we want to do that? One reason could be that when we want to switch to a different way of encrypting things (maybe use a stronger encryption algorithm), we’d only need to change it in this one place, in our module, and be done with it.

Another reason could be that we want the encryption algorithm to be somehow configurable, for example, in a configuration file that our application reads. This would then require additional logic that we wouldn’t want to duplicate across all the places where we need to encrypt something. We’d want all this to be kept in a central place.

Another reason could be that we want to move some clutter out of sight and hide it away in another file. So, we can focus on what our Person class does instead of having to look at the details of how exactly we encrypt things.

Here’s how we can create a meaningful module for our application and use it in the Person class:

Press + to interact
require 'digest'
module Encryption
def encrypt(string)
Digest::SHA2.hexdigest(string)
end
end
class Person
include Encryption
def initialize(name)
@name = name
end
def name
@name
end
def password=(password)
@password = password
end
def encrypted_password
encrypt(@password)
end
end
person = Person.new("Ada")
person.password = "super secret"
puts person.encrypted_password

This code prints out the same encrypted version of the password. That’s what we want!

We’ve moved the noisy details of the encryption algorithm to a module and then included the module in the Person class. This, at the very least, makes the encrypted_password method much easier to read.

We refer to the process of moving some logic (code) from one method to another as extracting a method. In our case, we’ve extracted the encrypt method from the encrypt_password method. Methods usually become shorter and more readable when we do this.