Modules and Mixins

18.1 Modules as Namespaces

module Payments
  VERSION = "1.0"
  class Invoice; end
  module Gateways
    class Stripe; end
  end
  def self.configure = yield(config)      # module function
end
Payments::Gateways::Stripe.new

18.2 Mixins: include, extend, prepend

M's methods become instance methods. M goes after the class in ancestors.

M goes before the class in ancestors (wraps/overrides methods, used for decorators)

M's methods become singleton methods of the receiver (class methods when used in a class)

Makes methods callable as M.method and private as mixins (like Math.sqrt)

module Greetable
  def greet = "Hello, I'm #{name}"
end

module Trackable
  def self.included(base)            # hook: add class methods when included
    base.extend(ClassMethods)
  end
  module ClassMethods
    def tracked_fields = @tracked_fields ||= []
    def track(*fields) = tracked_fields.concat(fields)
  end
end

class User
  include Greetable                  # instance methods
  include Trackable
  include Comparable                 # requires <=>
  attr_reader :name, :age
  track :name, :email
  def initialize(name, age) = (@name, @age = name, age)
  def <=>(other) = age <=> other.age
end

user = User.new("Ana", 30)
user.greet
User.tracked_fields
User.new("A", 20) < User.new("B", 30)    # Comparable gives <, >, between?, clamp

module Loud
  def speak = super.upcase + "!"      # prepend: runs BEFORE the class's method
end
class Dog < Animal
  prepend Loud
end

obj = Object.new
obj.extend(Greetable)                 # extend: adds module methods to a single object (or a class's class methods)

18.3 Method Lookup Path

Lookup order: singleton class → prepended modules → the class → included modules (last included first) → superclass chain → method_missing.

User.ancestors
# [User, Comparable, Trackable, Greetable, Object, Kernel, BasicObject]

18.4 Built-in Mixins

Comparable (define <=>), Enumerable (define each), Kernel (puts, require, loop), Forwardable (def_delegators), Singleton, Observable (removed from the default gems in 3.4; install the gem), DRb.

require "forwardable"
class Queue2
  extend Forwardable
  def_delegators :@items, :size, :empty?, :first, :each
  def_delegator :@items, :push, :enqueue
  def initialize = @items = []
end