Skip to content
AgileLogics
6 min read

Comparable and Enumerable

Prerequisites: Modules and mixins

Two built-in modules show the real power of mixins: define one method, include the module, and get dozens of methods for free.

Comparable — define <=>, get comparisons

class Version
  include Comparable
  attr_reader :parts

  def initialize(text)
    @parts = text.split(".").map(&:to_i)
  end

  def <=>(other)
    parts <=> other.parts
  end

  def to_s = parts.join(".")
end

a = Version.new("1.10.0")
b = Version.new("1.9.3")

p a > b
p a == Version.new("1.10.0")
puts [a, b, Version.new("2.0")].sort.join(", ")
puts [a, b].max
p b.between?(Version.new("1.0"), a)
true
true
1.9.3, 1.10.0, 2.0
1.10.0
true

Comparing the text "1.10.0" > "1.9.3" would give the wrong answer (text compares character by character). Comparing arrays of integers gives the right one — and Comparable turns that single <=> method into >, <, ==, between?, clamp, and sorting.

Enumerable — define each, get map, select, and more

class Playlist
  include Enumerable

  def initialize(*songs)
    @songs = songs
  end

  def each(&block)
    @songs.each(&block)
    self
  end
end

list = Playlist.new("Intro", "Blue", "Anthem", "Outro")

p list.map(&:upcase)
p list.select { |s| s.length > 4 }
p list.sort
p list.include?("Blue")
p list.first(2)
p list.count
["INTRO", "BLUE", "ANTHEM", "OUTRO"]
["Intro", "Anthem", "Outro"]
["Anthem", "Blue", "Intro", "Outro"]
true
["Intro", "Blue"]
4

&block in the parameter list captures the block passed to each as an object, so it can be handed on to the array's own each. The Procs and Lambdas lesson explains this in detail.

Common mistakes

  • Returning nil from <=> for objects that should be comparable. nil means "can't compare", and sort raises an error.
  • Subclassing Array to make a collection class. Including Enumerable and wrapping an array is simpler and safer.