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