Ruby Enumerable and Comparable
last modified October 18, 2026
Ruby's Enumerable and Comparable are mixin modules that
give your own classes a large set of behaviors for free. By including
Enumerable and defining each, you get methods such as
map, select, reduce, and
sort_by. By including Comparable and defining
<=>, you get <, between?,
clamp, and sorting support. This guide shows how to use both modules.
Enumerable in a Custom Class
To make a class enumerable, include the Enumerable module and define
an each method that yields its elements. Every Enumerable method is
then built on top of each. This example wraps an array in a custom
collection and calls several Enumerable methods on it.
class NumberCollection
include Enumerable
def initialize(*numbers)
@numbers = numbers
end
def each
return enum_for(:each) unless block_given?
@numbers.each { |n| yield n }
self
end
end
nums = NumberCollection.new(5, 3, 8, 1, 9, 2)
p nums.map { |n| n * 2 }
p nums.select(&:even?)
p nums.reduce(:+)
p nums.sort_by { |n| -n }
p nums.min_by { |n| (n - 4).abs }
p nums.each_slice(2).to_a
p nums.include?(8)
p nums.sort
p nums.first(2)
The return enum_for(:each) unless block_given? line lets callers
call each without a block and receive an Enumerator, which is how
methods like each_slice without a block work. Note that
min_by returns the element closest to 4, which is 5.
$ ./enumerable_custom.rb [10, 6, 16, 2, 18, 4] [8, 2] 28 [9, 8, 5, 3, 2, 1] 5 [[5, 3], [8, 1], [9, 2]] true [1, 2, 3, 5, 8, 9] [5, 3]
Grouping and Aggregating
Enumerable also provides grouping and aggregation helpers such as
group_by, partition, sum, and
each_with_index. This example builds a small inventory class that
includes Enumerable and uses these methods over its items.
class Inventory
include Enumerable
Item = Struct.new(:name, :category, :price)
def initialize
@items = []
end
def add(name, category, price)
@items << Item.new(name, category, price)
self
end
def each(&block)
@items.each(&block)
self
end
end
inv = Inventory.new
.add("apple", :fruit, 0.5)
.add("carrot", :vegetable, 0.3)
.add("banana", :fruit, 0.25)
.add("leek", :vegetable, 1.2)
p inv.group_by(&:category).transform_values { |items| items.map(&:name) }
p inv.sum(&:price).round(2)
p inv.partition { |i| i.price > 0.4 }.map { |g| g.map(&:name) }
p inv.each_with_index.map { |item, i| "#{i + 1}. #{item.name}" }
p inv.min_by(&:price).name
p inv.each_cons(2).count
Because each forwards its block to the internal array, every
Enumerable method works on Inventory without further code. The
partition call splits items into those costing more than 0.4 and
the rest.
$ ./enumerable_groups.rb
{:fruit=>["apple", "banana"], :vegetable=>["carrot", "leek"]}
2.25
[["apple", "leek"], ["carrot", "banana"]]
["1. apple", "2. carrot", "3. banana", "4. leek"]
"banana"
3
Lazy Enumerators
Enumerable#lazy returns an Enumerator that defers work until a result
is actually needed. Chained operations are evaluated element by element, so you can
work with infinite sequences. Calling first(n) or
to_a triggers evaluation.
# Infinite sequence of natural numbers
naturals = (1..Float::INFINITY).lazy
result = naturals
.map { |n| n * n }
.select { |n| n.even? }
.first(5)
p result
# Lazy enumerator from a custom Enumerable
class Countdown
include Enumerable
def each
n = 10
while n > 0
yield n
n -= 1
end
end
end
p Countdown.new.lazy.map { |x| x * 10 }.reject(&:odd?).first(3)
p Countdown.new.lazy.take_while { |x| x > 7 }.to_a
Without lazy, the first example would try to map an infinite range
and never finish. With it, only the squares needed to find five even values are
computed. Any class that includes Enumerable gets lazy too, as the
Countdown example shows.
$ ./enumerable_lazy.rb [4, 16, 36, 64, 100] [100, 90, 80] [10, 9, 8]
Comparable in a Custom Class
Include Comparable and define <=> (the spaceship
operator), which returns -1, 0, or 1, or nil when the objects cannot
be compared. Comparable then supplies <, <=,
>, >=, ==, between?, and
clamp. The example below compares versions by major and minor number.
class Version
include Comparable
attr_reader :major, :minor
def initialize(major, minor)
@major = major
@minor = minor
end
def <=>(other)
return nil unless other.is_a?(Version)
[major, minor] <=> [other.major, other.minor]
end
def to_s
"#{major}.#{minor}"
end
end
a = Version.new(1, 2)
b = Version.new(1, 10)
c = Version.new(2, 0)
puts a < b # true
puts a <= a # true
puts c > b # true
puts a == Version.new(1, 2) # true
puts b.between?(a, c) # true
puts Version.new(3, 0).clamp(a, c) # 2.0
versions = [c, a, b]
puts versions.sort.join(", ")
puts versions.min
puts versions.max
Comparing arrays with <=> is a convenient way to compare
multi-part values: the major numbers are compared first, and the minor numbers
break ties. Because Version now has a total order, sort,
min, and max work on arrays of versions. Note that
1.10 is greater than 1.2, since the minor number is
compared as an integer.
$ ./comparable_custom.rb true true true true true 2.0 1.2, 1.10, 2.0 1.2 2.0
Comparable with Sorting and Clamping
Once <=> is defined, your objects can be sorted, reduced to a
maximum, and bounded to a range. This example uses a temperature class. Note that
== comes from Comparable, while eql? is a separate method
and is not provided by it.
class Temperature
include Comparable
attr_reader :degrees
def initialize(degrees)
@degrees = degrees
end
def <=>(other)
degrees <=> other.degrees
end
def inspect
"#{degrees}C"
end
end
temps = [Temperature.new(21), Temperature.new(-4), Temperature.new(35)]
p temps.sort
p temps.max
p Temperature.new(10).between?(Temperature.new(0), Temperature.new(20))
p Temperature.new(50).clamp(Temperature.new(0), Temperature.new(30))
p Temperature.new(5) == Temperature.new(5)
p Temperature.new(5).eql?(Temperature.new(5))
clamp returns the upper bound when the value is above the range, so
50 becomes 30. The final line shows that eql? still uses object
identity by default, so two equal Temperature objects are not eql?
unless you define it yourself.
$ ./comparable_sort.rb [-4C, 21C, 35C] 35C true 30C true false
Source References
Learn more from these resources: Ruby Enumerable Documentation, Comparable Documentation, and Enumerator::Lazy.
List all Ruby tutorials.
Author
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.