Modules

    • as namespaces for defining other types, methods and constants
    • as partial types that can be mixed in other types

    An example of a module as a namespace:

    Library authors are advised to put their definitions inside a module to avoid name clashes. The standard library usually doesn’t have a namespace as its types and methods are very common, to avoid writing long names.

    To use a module as a partial type you use or extend.

    An include makes a type include methods defined in that module as instance methods:

    1. module ItemsSize
    2. def size
    3. items.size
    4. end
    5. end
    6. class Items
    7. include ItemsSize
    8. def items
    9. [1, 2, 3]
    10. end
    11. end
    12. items = Items.new
    13. items.size # => 3

    In the above example, it is as if we pasted the size method from the module into the Items class. The way this really works is by making each type have a list of ancestors, or parents. By default this list starts with the superclass. As modules are included they are prepended to this list. When a method is not found in a type it is looked up in this list. When you invoke super, the first type in this ancestors list is used.

    An extend makes a type include methods defined in that module as class methods:

    1. module SomeSize
    2. def size
    3. 3
    4. end
    5. end
    6. extend SomeSize
    7. end
    8. Items.size # => 3

    Both include and extend make constants defined in the module available to the including/extending type.

    Both of them can be used at the top level to avoid writing a namespace over and over (although the chances of name clashes increase):

    1. module SomeModule
    2. class SomeType
    3. end
    4. def some_method
    5. 1
    6. end
    7. end
    8. include SomeModule
    9. SomeType.new # OK, same as SomeModule::SomeType
    10. some_method # OK, 1

    A common pattern for modules is extend self:

    In this way a module can be used as a namespace:

    1. Base64.encode64 "hello" # => "aGVsbG8="
    1. include Base64
    2. encode64 "hello" # => "aGVsbG8="

    For this to be useful the method name should have some reference to the module, otherwise chances of name clashes are high.

    A module cannot be instantiated:

    1. module Moo
    2. end
    3. Moo.new # undefined method 'new' for Moo:Module

    Module Type Checking

    Modules can also be used for type checking.

    If we define two modules with names A and :

    These can be included into classes:

    1. class One
    2. end
    3. class Two
    4. include B
    5. end
    6. class Three < Two
    7. include A
    8. end
    1. one = One.new
    2. typeof(one) # => One
    3. one.is_a?(A) # => true
    4. one.is_a?(B) # => false
    5. three = Three.new
    6. typeof(three) # => Three
    7. three.is_a?(A) # => true
    8. three.is_a?(B) # => true

    This allows you to define arrays and methods based on module type instead of class:

    1. one = One.new
    2. two = Two.new
    3. three = Three.new
    4. new_array = Array(A).new
    5. new_array << one # Ok, One inherits module A
    6. new_array << three # Ok, Three includes module A