Modules in Ruby
05 Jul 2015Currently, one hundred percent of Year of Commits’ commits have been to Ruby repositories. Ruby has many strengths and is a very malleable language. Ruby can be written functionally or object oriented. I tend to lean toward the latter and find myself using a few different patterns regularly. One powerful tool Ruby provides is the use of Modules.
Modules
When code is to be shared between classes, a Module
is created to encapsulate the shared functionality. A very simple module looks something like this:
module Ripe
def ripe?
puts "this is ripe!"
end
end
extend
With the Ripe
module, there are two ways to include its methods in a class. We can extend
the module, which makes the method ripe?
a class method on the Banana
class:
class Banana
extend Ripe
end
Banana.ripe?
# => this is ripe!
include
Alternatively, we can include
the module, making ripe?
an instance method:
class Banana
include Ripe
end
banana = Banana.new
banana.ripe?
# => this is ripe!
That’s great right? Right! We have methods on methods on methods. As long as we can make modules for our instance and class methods separately, we will be golden. But, what about all the times that we need to define both class and instance methods in one module? Surely it would be crazy to have RipeClassMethods
and RipeInstanceMethods
right? That looks like amateur hour. There must be a better way.
def self.included
A very helpful method for dealing with included modules is the included
class method. This method is built in Ruby and any class has access to it. By using the included
method, it is possible to mix class and instance methods within a single module.
When using self.included
, we are able to determine which methods are accessible on the instance and on the class. We will use a new inner module named ClassMethods
to encapsulate our desired class methods. In our example below, the single parameter base
is the class in which the Ripe
module is included.
module Ripe
def self.included(base)
# Just like in normal module extension,
# the extend method is used to make all methods in
# ClassMethods into proper class methods on base
base.extend(ClassMethods)
end
module ClassMethods
def ripe?
puts 'this is ripe!'
end
end
end
Now, including the functionality from Ripe
is the same as the include
example above:
class Banana
include Ripe
end
Banana.ripe?
# => this is ripe!
With this new structure, adding instance methods is as simple as adding them outside of the ClassMethods
module.
module Ripe
def self.included(base)
base.extend(ClassMethods)
end
def color
'yellow'
end
module ClassMethods
def ripe?
puts 'this is ripe!'
end
end
end
class Banana
include Ripe
end
Banana.ripe?
# => this is ripe!
banana = Banana.new
banana.color
# => yellow
Boom, boom, pow, we have some nicely organized shared code. We are now able to easily define instance and class methods for our various important fruit related modules.