Ruby Modules

by
January 17, 2011

There are two common usages for modules in Ruby: namespacing and mixing in.

An example of namespacing can be seen in the Math module:

Math::PI         # 3.14159....
Math.log10(100)  # 2.0

Mixing in a module will extend that class with additional constants and methods. Here’s an example where we’ll create our own module and mix it in:

module Taggable
  DEFAULT_TAG = 'none'
  
  def tag_as(tag)
    # since methods are mixed in, we have access to any instance methods/variables
    @tag = tag
  end
end

class Bookmark
  attr_reader :tag
  include Taggable
end

bookmark = Bookmark.new
bookmark.tag_as('recent')
bookmark.tag                  # 'recent'

 

Private instance/class methods for modules

Defining a private instance method in a module works just like in a class, you use the private keyword.

module Taggable
  private
  def finalize!
    # ...
  end
end

class Bookmark
  include Taggable
end

bookmark = Bookmark.new
bookmark.finalize!           # NoMethodError: private method `finalized!` called

You can define private class methods using the private_class_method method.

module TagUtils
  def self.uses_private_method
    default_tag
  end

  def self.default_tag
    'none' 
  end
  private_class_method :default_tag
end

TagUtils.default_tag          # NoMethodError: private method `default_tag` called
TagUtils.uses_private_method  # 'none'

 

Mix in class methods

Whereas include will add instance methods, extend will add class methods.

module Taggable
  def default_tag
    'none'
  end
end

class Bookmark
  include Taggable
end

class Page
  extend Taggable
end

Bookmark.new.default_tag   # 'none'
Bookmark.default_tag       # NoMethodError

Page.new.default_tag       # NoMethodError
Page.default_tag           # 'none'

You’ve probably rarely used the extend method to add class methods. A common Ruby idiom for module authors is to abstract that away using an included hook so users won’t have to remember to use extend or include.

Anytime a class includes a module, it will call the class method self.included on the module. This lets us include any instance methods and class methods all at once:

 module Taggable
  def self.included(base)  # base is the class which is including this module
    base.extend(ClassMethods)
  end

  module ClassMethods
    # class methods go here
    def default_tag
      'none'
    end
  end

  # instance methods go here
  def tag_as
    # ...
  end
end

class Bookmark
  include Taggable
end

Bookmark.default_tag             # 'none'
Bookmark.new.tag_as('awesome')

 

Overriding mixed in methods

When you’re including a module, it’s similar to inserting a superclass between your current class and its superclass. The super keyword works as expected. If you’re dealing with a complicated hierarchy, you’ll have to make sure modules are being mixed in the correct order. Let’s take a look:

module Bar
  def bar
    'bar'
  end
end

module Bar2
  def bar
    "#{super}2"
  end
end

class Foo
  include Bar
  include Bar2
  def bar
    "#{super}3"
  end
end

Foo.new.bar  # "bar23"

Foo implicitly inherits from Object. But when you mix in Bar and Bar2, their definition of bar gets inserted between Foo and Object:

Foo.ancestors  # [Foo, Bar2, Bar, Object, Kernel]

When Foo#bar gets invoked, Ruby will first look at Foo for the definition. If it wasn’t there, it would continue looking up the chain until it reaches Kernel. Since it was there, it uses Foo#bar. The super keyword is a reference to its ancestor’s #bar implementation.

Handling naming collisions

What if your module defines a new constant which overrides with one of Ruby’s?

module MarshallableType
  class Float; end;
  
  Float # MarshallableType::Float
end

The :: operator lets you traverse namespaces to find the correct constant you need. Ruby’s top-level global namespace is just Object, so you can still access Ruby’s float inside MarshallableType.

module MarshallableType
  class Float; end;

  Float           # MarshallableType::Float
  Object::Float   # plain Float
  ::Float         # shortcut for Object::Float
end