My Journey into Ruby: Modules
Tags: ruby, tutorial, programming, webdev
Published: 2021-04-05 12:07:25

Let's talk about modules in Ruby, how they differ to classes, why are they useful and how does the Ruby method lookup works.

Modules in Ruby are similar to classes, they hold multiple methods. However, the modules cannot be instantiated as classes do, the modules do not have the method `new`.

We can think of modules as **mix-ins**, they allow you to inject some code into a class. The mix-in exports the desired functionality to a child class, without creating an "is a" relationship. In Ruby, the main difference between inheriting from a class and mixing a module is that you can mix in more than one module.

The [Well-Grounded Rubyist book](https://www.amazon.com/Well-Grounded-Rubyist-David-Black/dp/1617295213) has a really good example about when a module may be needed.

> 💡 When you're designing a program and you identify a behavior or set of behaviors that may be exhibited by more than one kind of entity or object, you've found a good candidate for a module.

Let's review the basics of module creation. We will use "stack-likeness" as our main example. We want to create a module that introduces stack-like behavior to any class.

> ℹ️ A [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is a one dimensional data structure that follows a particular order for the operations to be performed on it: LIFO (Last In First Out), the last element to be added into the stack will be the first element to be queried on the stack.

## Module Creation

Writing a module follows almost the same syntax that we use to create classes, the only difference is that we need to start the definition using the `module` keyword instead of the `class` keyword:

```ruby
module Stacklike
	def stack
		@items ||= []
	end

	def push(obj)
		items.push(obj)
	end

	def pop
		items.pop
	end
end
```

What we did here is pretty simple. We're creating an instance variable called *@stack* that is an array representing our stack (1D data structure we talked about above) and we then abstract the *push* and *pop* behaviors of the stack into the *push* and *pop* methods of the module.

## Mixing the Module Into a Class

In order to mix the module into a class we are going to make use of one of three different keywords: `include`, `prepend` and `extend`.

> ⚠️ For this example, I'm going to show how to use the `include` keyword to add Stack-likeness into our class, to understand how the other keywords work we'll need to go through how the **method lookup** works in Ruby and we'll do that later in this article.

```ruby
class Stack
	include Stacklike
end
```

That's it. That's all that is needed in order to mix our *Stacklike* module into our *Stack* class, and now it can behave like a stack.

```ruby
stack = Stack.new
stack.push("First item")
stack.push("Second item")
stack.push("Third item")
puts "Objects currently in the stack:"
puts stack.items
last_item = stack.pop
puts "Removed from the stack:"
puts last_item
puts "Objects currently in the stack:"
puts stack.items
```

If we execute this code, we'll see that the stack behavior is currently being applied to our *Stack* class and the Stack-likeness behavior can be introduced to any other class that we want in our codebase, it is easily reusable.

> 💡 Rubyists often use adjectives for module names in order to reinforce the notion that the module defines a behavior.

## Method Lookup Path

The method lookup path is a mechanism in which Ruby starts "bubbling up" in the "object chain" in order to find what object or module is the one that contains the method that we're currently calling. This is what's being used when we call `object.pop` or `object.items` in our past example, those methods are not explicitly defined in **Stack**, but they're found in the method lookup path of the Class by including the **Stacklike** module.

Consider the following code:

```ruby
module M
	def x
		puts "Hey! I'm the module M"
	end
end

class B
	def x
		puts "Hey! I'm the class B"
	end
end

class A < B
	includes M
end
```

We are creating a class (**A**) that inherits from another class (**B**) and also mixes the behavior that's provided by a module (**M**). Now, we can instantiate the class A and call the method X, by following the Method Lookup Path, we'll get "*Hey I'm the module M*" as the result.

```ruby
a = A.new
a.x # Hey I'm the module M
```

![Include Method Lookup Path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cthu43vvtyusrmvmu41e.png)

The Class A object will try to find a method to execute based on the message that has been received (*x*). If Ruby has looked up all the way in the object chain until it reaches out *Kernel* or *BasicObject* and still hasn't found it, then it won't be found.

> ℹ️  The point of *BasicObject* is to have as few instance methods as possible, so it doesn't provide much functionality. Most of the functionality or Ruby's fundamental methods is actually found in the *Kernel* module, that are included in all objects that descend from *Object*.

By looking at the diagram above, you'll note that the way the method *x* is found in the example is by following the path: `[A, M, B, Object, Kernel, BasicObject]` and as the method *x* is found in *M*, it will use that instead of using the one found in *B*. You can use the `ancestors` method to find this path in any object, it is provided by the *Kernel* module.

> ℹ️ If two modules are included in the same class and they both contain a method with the same name, they're going to be searched in reverse order of inclusion. The last mixed-in module is searched first.

## Prepend and Extend Keywords

When mixing-in the first module into the class, I told that the `prepend` and `extend` keywords could be used to that as well. Let's see the differences between them.

### Prepend

It works almost the same as `include`, the difference is that when you prepend a module to the class, the object will look for the method in the module first, before looking for it in the class.

Consider this change in the code above:

```ruby
class A < B
	prepend M

	def x
		puts "Hey! I'm the class A"
	end
end
```

The method lookup path will change to `[M, A, B, Object, Kernel, BasicObject]`.

![Prepend Method Lookup Path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zut1cwk5kzzkmt3y4qwn.png)

### Extend

Both `include` and `prepend` will make the methods of the modules available as instance methods of the class. `extend` works a bit different by making module's methods available as class methods instead. Extending an object doesn't add the module into the ancestor chain.

## Super Method

There's this keyword called `super` that we can use inside the body of a method definition. What this keyword does is that it jumps to the next-highest definition of the current method in the method lookup path.

Consider the following change in the example code:

```ruby
module M
	def x
		puts "Hey! I'm the module"
		puts "But I'm going to call the next higher-up method..."
		super
		puts "Back in the module"
	end
end
```

Calling the method x will result in something like:

```ruby
a = A.new
a.x

# Hey I'm the module
# But I'm going to call the next higher-up method
# Hey I'm the Class B
# Back in the module
```

Because the `super` keyword is going to call the method *x* that is found in the Class B which is the next object that is available in the method lookup path in this example.

It's also important to note that the `super` keyword handles arguments in a different way as methods would do:

- When called with no argument list, it will automatically forward the arguments that were passed to the method from which it's called.
- When called with an empty argument list (`super()`), it sends no arguments to the higher up method, even if there were arguments passed to the current method.
- When called with specific arguments (`super(1, 2, 3)`), it sends exactly those arguments to the higher up method.

## When to Use Mix-Ins vs Inheritance

Having both inheritance and modules means that you have a lot of choice, but having a lot of choice also means that you also must be very careful about the considerations both this approaches introduce.

- As noted at the beginning of this post, *modules don't have instances*. **Entities** or **things** are better modeled using *classes*, while **behaviors** or **properties** are better encapsulated using *modules*.
- *Classes* can have a **single superclass**, but can **mix in as many** *modules* **as** **needed**.

You may like to break everything into separate modules, because you think something that you write for one entity may be useful in another entities in the future. But the overmodularization also exists. You've got the tools, and is up to you to consider how to balance them.

---

Hope you've liked this post, I learned a lot while studying modules and I tried to summarize it as much and as easy I could in this post.

If you've got any feedback or question about this post, please add it as a comment. I'm sure we all could learn about this.

Thanks for your time ❤️👋