4.6. Modules and Mixins

The word "module" has been mentioned here and there a few times so far. It's time to take a closer look at a fundamental part of Ruby programming.

As mentioned before, modules serve two purposes. They act as namespaces that prevent name collision and are used as a way to add functionalities to classes that would otherwise be limited by the single inheritance nature of Ruby's object model.

A series of similarities between classes and modules are as follows:

  • Classes are constants and so are modules.

  • Classes are defined through the class keyword and modules are defined with the module keyword.

  • Classes are instances of the Class class, and modules are instances of the Module class.

  • Classes act as namespaces. Two identical methods defined within two unrelated classes are not going to pose a problem. The same is true for modules.

  • Classes can be nested, and so can modules.

  • The Class class inherits from the Module class, so it can be said that every class is also a module.

On the other hand, a few fundamental differences also exist:

  • Classes can be instantiated. Modules cannot.

  • Classes can have a superclass and subclasses; as such they yield a hierarchy tree. Modules do not, because they don't have a parent or any children.

  • Modules can be used as mixins, but classes cannot.

4.6.1. Modules Act as Namespaces

If you are a .NET developer, you are no doubt familiar with the concept of namespaces. The basic idea is to group constants, variables, methods, and classes into well-organized units. In C# and VB you'd use namespace, in Ruby you use module:

module MyModel
end

Modules, like classes, are constants, and as such need to start with a capital letter. It is also customary to adopt the CamelCase notation for module names (just as it is for classes).

Now define a module and a constant as follows:

module Physics
  EARTH_MASS = 5.9742e24 # In Kg
end

How do you access that constant now? In Ruby you access methods through a dot operator and constants through ::. This means that you'll have access to the value of that EARTH_MASS constant in the following way:

puts Physics::EARTH_MASS   # Prints 5.9742e+024

You could start to add a series of constants to the module; in fact, given its name, it is plausible to assume that the module would take care of a lot of physics-related functionalities. Imagine that this module would be developed to contain dozens of physics constants, a few classes, and many methods. It would be much better to organize all the physics constants into a module called Constants.

Modules can be nested, so you can achieve this quite easily. For example:

module Physics
  module Constants
    EARTH_MASS = 5.9742e24 # Kg
    # ...
    AVOGADRO = 6.0221415e23 # mol⁁-1
    # ...
  end
end

Now how can you get access to the value of EARTH_MASS or AVOGADRO? Again, in Ruby you access constants through the :: operator; it doesn't matter if the constant happens to be a numeric one, a class, or a module. You start with the name of the outermost module (Physics), add a couple of colons to access the constant/module Constants, and finally, add another pair of colons to gain access to the two constants EARTH_MASS and AVOGADRO:

puts Physics::Constants::EARTH_MASS   # Prints 5.9742e+024
puts Physics::Constants::AVOGADRO     # Prints 6.0221415e+023

You can nest modules by simply nesting their definitions:

module A
  # ... A ...
  module B
    # ... B ...
    module C
      # ... C ...
      module D
        # ... D ...
      end
    end
  end

  module F
    # ... F ...
  end
end

As long as the intermediary modules are already defined (A::F in this case), you can also define a nested module in this way:

module A::F::G
  # ...
end

Generally speaking you wouldn't want to overdo it, but two or three nested namespaces are not uncommon.

"Standalone" class methods can be defined within modules in the usual "singleton manner":

module MyModule
  def self.method1
  end

  def self.method2
  end

  def self.method3
  end
end

Or alternatively:

module MyModule
  def MyModule.method1
  end

  def MyModule.method2
  end

  def MyModule.method3
  end
end

In both equivalent cases, the dot operator is used to access the method directly contained in a namespace:

MyModule.method1

If MyModule were a module nested within, say, AnotherModule, then method1 would be accessible through MyModule::AnotherModule.method1. AnotherModule::MyModule is the receiver for the method.

Class methods defined within modules, just like class methods defined for classes, can share data through class variables (for example, @@var). Modules don't inherit from each other, so using them inside of the definitions of class methods within modules (but outside of classes) makes them less risky.

Modules can also contain classes, as you'd probably expect if you are coming from a .NET background.

Just as for the classes, you can reopen modules as well, and monkey patch them if needed.

Before moving onto the real deal with modules (their usage as mixins) you should know how you can load Ruby files (including those that contain modules). Assume that you defined the Physics module within a physics.rb file.

The name of a file can be arbitrary and contain any Ruby code you like. If your file contains only one main module (and its possible nested modules and classes) though, it is customary to call the file with the name of that module (in lowercase).

To load it from a program in the same directory, you can run:

require 'physics'

The file will actually be loaded only once, and all of its content (modules, classes, methods, and so on) will be available in your program.

require accepts absolute paths as well. See ri Kernel#require for more information about this.

4.6.2. Modules Act as Mixins

Modules are a powerful tool in the hands of a competent Ruby programmer, because they can act as mixins. You won't find "mixins" in the dictionary, but the term comes from "mix" and "in," which is a very apt description of what modules can do; they can add functionalities to existing classes by "mixing in" a series of methods.

Mixins are modules whose code can be included in a class (and in another module). Consider a basic example. The following is a module with a method:

module Logger
  def log
    puts "#{Time.now}: #{self.inspect}"
  end
end

All the log method does is print the current time and object. Note how this method is not a class method (because it wasn't defined as self.log or Logger). log is an instance method and you can't really instantiate modules. What this means is that you are not able to invoke Logger.log. What you can do, though, is to add the functionalities of the Logger module to a class, by adding its instance methods to the class. In this example, the added feature will just be the log method, but the same principle applies to arbitrarily complex code as well.

You can include the module within any class:

class Array
  include Logger
end

This adds the instance method log to the class Array. Now you can run:

array = []
10.times { array << rand(100) }
array.sort!
array.log

and obtain something that resembles this in your output:

Wed Jul 02 00:31:32 -0400 2008: [5, 38, 47, 51, 63, 73, 83, 84, 90, 95]

Interestingly, you can verify that array.is_a? Logger evaluates to true, whereas array.instance_of? Logger will return false.

Notice that the method is general enough to be used by any class. By defining it in a mixin, instead of a specific class, you can include that functionality in any class that may need it. This turns out to be extremely powerful, even if the simplicity of the example may be misleading and cause you to think otherwise. A class can include several mixins, and as such obtain functionalities derived from several modules. This approach is simple, flexible, and essentially provides the benefits of multiple inheritance.

Classes like Range, Array, and Hash all include the Enumerable module, whereas String (at least in Ruby 1.8) includes both Comparable and Enumerable. Enumerable is definitely one of the most used mixins. The reason for this is that it provides many iterators "for free" to your classes, as long as you implement a required each method. If you do, Enumerable can infer the right behavior for its iterators.

To include multiple mixins, you can pass a list to include:

class MyClass
  include Enumerable, Comparable, MyCustomMixin
end

4.6.2.1. include Versus extend

A lot of concepts were introduced in the past couple of sections, so let's recap the type of methods that you can define directly inside of a module. If you define a singleton method in the module (for example, def self.my_method), you can then invoke it like a utility function, with the module as a receiver (for example, MyModule.my_method) acting mainly as a namespace for logically similar functions/methods. If you define an instance method in the module (for example, def my_method), you can then add it to classes as an instance method by passing the mixin (that is, the name of the module) to include within the definition of the class.

You may notice that a third type of method is missing. What about actual singleton methods that can be invoked on a specific object, or perhaps more interestingly, on a class object? Yes, having arrived at this point, you are essentially missing a way to define class methods that are invoked on actual classes.

Ruby provides you with extend exactly for that purpose:

class MyClass
  extend Logger
end

MyClass.log

Notice how the method log, defined as an "instance method" in the module Logger, becomes a class method of the MyClass class. Behind the scenes, all the instance methods defined in Logger get added to the eigenclass of MyClass. This means that, if instead of doing it with a class object, you added extend Logger to a specific instance of an object, the methods would become regular singleton methods for that object. For most practical purposes, just remember: use include to obtain instance methods, and extend to get class methods for your class.

Investigate the module_function method if you'd like to automatically obtain a method that can be called on a module (for example, MyModule.my_method) from an instance method that you defined in the module. This way, you'll be able to obtain mixin behavior with include/extend in your classes, while still being able to access the methods like you would with a module that acts purely as a namespace.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.240