Modules are an important part of the Ruby programming world. They offer multiple benefits such as the ability to:
- "mix-in" methods across multiple classes
- DRY out your code
- bundle logically related objects together
What code would look like without modules
Take a look at this sample code and notice what's wrong with it:
#car.rb
class Car
def horn
puts "beep beep!"
end
end
#truck.rb
class Truck
def horn
puts "beep beep!"
end
end
A couple things look a little funky here. We have 2 different classes present and they both define a method called horn
.
First off, this code isn't DRY. We define the same method in 2 different classes that serve the same exact function. Second, the horn
method isn't exclusive to the Car
or Truck
classes. They can make use of this method, but so could a lot of other objects. Who knows, further down the line we may be creating Van
and Bus
classes that also need a horn
method. It wouldn't make sense to re-define it for all of them too.
And this, is where modules come in handy.
A "bucket" for methods
One way of describing modules that really helped me was thinking of them as "buckets" for methods. They're similar to classes in this way, but unlike classes they can't be instantiated.
So, let's create a new module and include
it in our 2 classes.
#car.rb
class Car
include AutomobileSounds
end
#truck.rb
class Truck
include AutomobileSounds
end
#automobile_sounds.rb
module AutomobileSounds
def horn
puts "beep beep!"
end
end
We can hop into irb and test out the functionality:
> load 'automobile_sounds.rb'
=> true
> load 'car.rb'
=> true
> Car.new.horn
=> beep beep!
Pretty sweet. Our new module can now be accessed by both of our other classes. We've DRY'ed up our code, made it more elegant, and made it easier to use as our application grows.
As I mentioned earlier, unlike classes, modules can't be instantiated so AutomobileSounds.new
will give us an "undefined method" error. This module is merely a "bucket" for methods.
It's also important to note that using the include
method gives us access to a module as an instance method. For example, we used include AutomobileSounds
in the Truck
class so we can now call Truck.new.horn
. We cannot, however, call Truck.horn
, which would be a class method. If we wanted to be able to call a class method, we would have to extend
the module, rather than include
it. Make sense?
Closing
As always, please let me know if anything doesn't make sense or if you notice anything that needs fixing. You can reach me quickly @gregelizondo. I'm always open to hearing feedback. Thanks!
If you prefer a video explanation of this concept, be sure to check out Richard Schneeman's great video on YouTube: Using Modules in Ruby. This post is basically a written version of his lesson.