Modules in Ruby are a great place to store shared behavior and data used throughout a program. Writing a module is a convenient way to abstract out shared methods from multiple classes, thereby making the classes more DRY and manageable.
Some of the most popular modules in the Ruby core are
Enumerable. Yes, a module is the reason Rubyists across the world can enjoy
each and other higher level iterators like
Sinatra are two other popular modules used in the Rails and Sinatra web frameworks, respectively.
Why Use Modules?
DHH likes modules. And they’re cool.
How to Name a Module
Names are important in programming. And since modules are known in technical parlance as “namespaces”, module names are doubly important… and doubly spacious.
Like classes, modules in Ruby are capitalized in CamelCase. Whereas classes tend to be singular nouns (
Artist), module names tend to be adjectives (
Catlike). Modules often have unique names that describe the behaviors, characteristics, or qualities shared by multiple classes.
These naming conventions make sense because, unlike classes, modules cannot be instantiated. You can make a new
Person, but you cannot make a new
In your Rails app, file your modules in the
Ineritance via Modules
Direct class-to-class inheritance in Ruby is a tricky subject: only single inheritance is allowed. For example, this is valid Ruby:
class Mammal end class Animal < Mammal end class Dog < Animal end Dog.ancestors #=> [Dog, Animal, Mammal, Object, Kernel, BasicObject]
but this is not:
class Animal end class MansBestFriend end class Dog < Animal end class Dog < MansBestFriend end #=> TypeError: superclass mismatch for class Dog Dog.ancestors #=> [Dog, Animal, Object, Kernel, BasicObject]
Modules solve the problem of multiple inheritance in an interesting way. A class can call the
extend method to inherit a module’s class methods, and a class can call the
include method to inherit a module’s instance methods.
As a result, it is good practice to write modules in the following way:
module Generic module ClassMethods # class methods go here end module InstanceMethods # instance methods go here end end
The methods in the
Generic module can be mixed in to a class as follows:
class Student extend Generic::ClassMethods include Generic::InstanceMethods end
Zombies provide a useful analogy for thinking about the relationship between classes and modules. Consider the
class Person attr_accessor :name def initialize(name) self.name = name end def occupation "Being a human." end end
We can instantiate a new person as follows:
chris = Person.new("Chris") #=> #<Person:0x0000010185eba0 @name="Chris"> chris.name #=> "Chris" chris.occupation #=> "Being a human."
Now let’s say that a zombie pandemic breaks out. First thing’s first: how do we refactor our code?
We could rename our
Person class to
Zombie or write a new
Zombie class, but neither of these options feel totally right. Instead, it would seem more appropriate for instances of the
Person class to adopt zombie behavior in addition to the original behaviors of the
Zombified module is the obvious choice here: it is an abstraction encapsulating shared behavior across (potentially) multiple classes, and it does not need to be instantiated.
module Zombified module ClassMethods def status_of_the_human_race "doomed" end end module InstanceMethods def preferred_source_of_energy "BRAAAAIIINNNNSSSS!!!!!" end def is_alive? false end end end
When a module is
included into a class, instances of that class have access to the instance methods of both the class and the module mixed in to the class.
class Person include Zombified::InstanceMethods end chris = Person.new("Brains") #=> #<Person:0x00000102250618 @name="Brains"> chris.name #=> "Brains" chris.preferred_source_of_energy #=> "BRAAAAIIINNNNSSSS!!!!!" chris.is_alive? #=> false
chris has access to both the instance methods of the
Person class and the instance methods provided by the
Zombified::InstanceMethods module. In
chris’s method lookup chain, the methods in
Person come before the methods in the
Person.ancestors #=> [Person, Zombified::InstanceMethods, Object, Kernel, BasicObject]
An important takeaway is that method names in a module should not conflict with those of the method names in the classes they are mixed into. Instead, choose more abstract and granular method names.
Uh oh. The zombie pandemic has spread across the planet. What’s the status of the human race, then?
Person.status_of_the_human_race #=> NoMethodError: undefined method `status_of_the_human_race' for Person:Class
Right. Time to refactor our code!
class Person extend Zombified::ClassMethods end Person.status_of_the_human_race #=> "doomed"
Humanity is doomed, thanks to our handy module.
From here it’s easy to zombify objects in other classes:
class Dog include Zombified::InstanceMethods end fido = Dog.new #=> #<Dog:0x0000010326e428> fido.preferred_source_of_energy #=> "BRAAAAIIINNNNSSSS!!!!!" fido.is_alive? #=> false
Modules are fun! I hope to do another blog post about some of the other cool and more metaprogramm-y things you can do with modules, but for now check out some of the links below.