Ruby Modules

Modules as Namespaces

Prevent name collisions
module Network
  class Scanner
    def initialize(range)
      @range = range
    end

    def sweep
      puts "Scanning #{@range}"
    end
  end
end

module Security
  class Scanner
    def audit(target)
      puts "Auditing #{target}"
    end
  end
end

# No collision
net = Network::Scanner.new("10.50.1.0/24")
sec = Security::Scanner.new

Mixins

include adds instance methods, extend adds class methods
module Pingable
  def reachable?
    system("ping -c 1 -W 2 #{ip} > /dev/null 2>&1")
  end
end

module Loggable
  def log(message)
    puts "[#{Time.now.strftime('%H:%M:%S')}] #{self.class.name}: #{message}"
  end
end

class Server
  include Pingable
  include Loggable
  attr_reader :hostname, :ip

  def initialize(hostname, ip)
    @hostname = hostname
    @ip = ip
  end

  def check
    log("Checking #{hostname}")
    reachable? ? log("UP") : log("DOWN")
  end
end

srv = Server.new("vault-01", "10.50.1.40")
srv.is_a?(Pingable)          # => true

Prepend

prepend inserts module before class in method resolution
module Auditable
  def save
    puts "AUDIT: saving #{self.class.name}"
    super                           # calls the original save
    puts "AUDIT: saved"
  end
end

class DeviceConfig
  prepend Auditable

  def save
    puts "Saving configuration..."
  end
end

DeviceConfig.new.save
# AUDIT: saving DeviceConfig
# Saving configuration...
# AUDIT: saved

DeviceConfig.ancestors
# => [Auditable, DeviceConfig, Object, Kernel, BasicObject]

Enumerable Mixin

Adding Enumerable to custom classes
class DeviceList
  include Enumerable

  def initialize
    @devices = []
  end

  def add(device)
    @devices << device
    self
  end

  def each(&block)
    @devices.each(&block)
  end
end

list = DeviceList.new
list.add("sw-01").add("sw-02").add("rtr-01")
list.map(&:upcase)            # => ["SW-01", "SW-02", "RTR-01"]
list.select { |d| d.start_with?("sw") }
list.count                    # => 3