RbRuby · Lesson 8 of 11

Modules & Mixins

Ruby solves the multiple-inheritance problem with modules. You `include` a module and its methods become instance methods. `extend` makes them class methods. This is called a mixin and it's genuinely elegant.

Ruby
# Modules serve two purposes:
# 1. Namespacing (organizing code)
# 2. Mixins (sharing behavior across classes)

module Greetable
  def greet
    "Hello! I'm #{name}."   # assumes the including class has a name method
  end

  def farewell
    "Goodbye from #{name}!"
  end
end

module Serializable
  def to_json_str
    vars = instance_variables.map do |var|
      ""#{var.to_s[1..]}": "#{instance_variable_get(var)}""
    end
    "{ #{vars.join(", ")} }"
  end
end

class Person
  include Greetable
  include Serializable

  attr_reader :name, :age

  def initialize(name, age)
    @name = name
    @age  = age
  end
end

alice = Person.new("Alice", 30)
puts alice.greet         # Hello! I'm Alice.
puts alice.farewell      # Goodbye from Alice!
puts alice.to_json_str   # { "name": "Alice", "age": "30" }
Ruby
# Comparable — include it and define <=> to get all comparison operators free
class Temperature
  include Comparable

  attr_reader :degrees

  def initialize(degrees)
    @degrees = degrees.to_f
  end

  # <=> is the "spaceship operator" — required by Comparable
  def <=>(other)
    degrees <=> other.degrees
  end

  def to_s
    "#{degrees}°"
  end
end

temps = [Temperature.new(100), Temperature.new(0), Temperature.new(37)]
puts temps.min        # 0.0°
puts temps.max        # 100.0°
puts temps.sort.inspect
puts Temperature.new(37) > Temperature.new(20)   # true
puts Temperature.new(37).between?(Temperature.new(36), Temperature.new(38))  # true

# Enumerable — include it and define each() to get map/select/reduce/etc.
class NumberSet
  include Enumerable

  def initialize(*nums)
    @data = nums
  end

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

ns = NumberSet.new(3, 1, 4, 1, 5, 9)
puts ns.sort.inspect    # [1, 1, 3, 4, 5, 9]
puts ns.select(&:odd?).inspect  # [3, 1, 1, 5, 9]
puts ns.min             # 1
✦ Tip
The Comparable and Enumerable modules are two of Ruby's most powerful standard mixins. If your class represents ordered values, include Comparable and define `<=>`. If it contains a collection, include Enumerable and define `each`. You instantly get dozens of methods for free.