Modules in Ruby05 Jul 2015
Currently, 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.
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
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
class Banana extend Ripe end Banana.ripe? # => this is ripe!
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
RipeInstanceMethods right? That looks like amateur hour. There must be a better way.
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.
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
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.