Modules and Mixins

Modules and Mixins#

Modules#

Module in Ruby this is a way to group some classes, functions, constants together. It is quite similiar to the class, but module can not have an instance or the subclasses - it is just a namespace for a group of objects. Module is defined by keywords module … end. Below is an example of Network module containing method, class and constant:

module Network                            # module name
  PROTOCOLS = [:http, :ftp, :telnet]      # module constant
  def Network.is_a_protocol?(protocol)    # module method is defined like the class method
    PROTOCOLS.include? protocol           # module constact is accessible from the module method
  end
  class Ip                                # module class
    def initialize(p1, p2, p3, p4)
      @p1 = p1; @p2 = p2; @p3 = p3; @p4 = p4
    end
    def to_s
      "#{@p1}.#{@p2}.#{@p3}.#{@p4}"
    end
    def inspect
      "IP: #{to_s}"
    end
  end
end

To access the module method use Module_name.method_name, Module_name::CONSTANT_NAME for constants and Module_name::ClassName for classes.

Network::PROTOCOLS
#=> [:http, :ftp, :telnet]

Network.is_a_protocol? :ftp
#=> true

ip = Network::Ip.new 192, 168, 1, 1
#=> IP: 192.168.1.1

But it would be annoying to write a module name many times, especially when you are writing a long script. To avoid this, you may insert all the things from the module using include ModuleName statement to treat all the stuff like the local objects:

include Network
#=> Object

PROTOCOLS
#=> [:http, :ftp, :telnet]

is_a_protocol? :ftp
#=> true

ip = Ip.new 192, 168, 1, 1
#=> IP: 192.168.1.1

Math Module#

There is a build in module Math contains trigonometric and transcendental function (see documentation ri Math for complete list of methods). It is good to include Math if you are planning to do more work with this module.

Math.sin(Math::PI/2)
#=> 1.0

include Math
#=> Object

sin(PI/2)
#=> 1.0

sqrt 2
#=> 1.4142135623730951

Etc Module#

Another interesting built-in module is called Etc - a Ruby way to access information stored in /etc/passwd and /etc/group.

require 'etc'               # you must load the file with Etc module before you can use it
#=> true

Etc.getlogin                # Etc.getlogin returns the current user name
#=> "grych"

Etc.getpwnam(Etc.getlogin)  # Etc.getpwnam returns object containing all the information from passwd
#=> #<struct Struct::Passwd name="grych", passwd="********", uid=501, gid=20, gecos="Tomasz Gryszkiewicz", dir="/Users/grych", shell="/bin/zsh", change=0, uclass="", expire=0>

Etc.getpwnam('grych').gecos # and this is how you can access this structure
#=> "Tomasz Gryszkiewicz"

Mixins#

While discussing objects we talked about inheritance: to remind, object can be a subclass of the other object, it inherits all the methods, variables from the other class. But object can have only one superclass, so if you want to use some methods from few different sources, you must use Mixin. Mixin it is a module to be loaded into the class. All the module methods, constants will be included to the class.

To have an example lets create the mixin called Pingable with method alive? returns true if ping to the server works.

module Pingable
  def alive?
    p = `ping -c 1 -t 10 #{self.full_name}` # like in Shell you can run any command by putting it in ``
    p.include? '1 packets received'         # if output of ping command include this string, returns true
  end
end

self.fullname in the code above does not refer to the mixin, but to the instance of an object, to which this mixin will be included. This is a kind of a protocol: we assume, that all object which want to include Pingable must have defined full_name method.

Let’s go and load the mixin to our Server class:

class Server
  include Pingable              # include previously defined mixin
  def initialize(name, domain)
    @name = name
    @domain = domain
  end
  def full_name                 # mixin will call that function
    @name + @domain
  end
end

s = Server.new('www', '.startrek.com')
#=> #<Server:0x007fce68aee3e8 @name="www", @domain=".startrek.com">
s.alive?
#=> true

Server.new('www', '.starwars.com').alive?
#=> false