The Enumerable Module
Ruby’s Enumerable module is one of the most powerful and frequently used features in the language. It provides a collection of methods for iterating, searching, transforming, and aggregating data. Any class that implements an each method can include Enumerable and gain access to dozens of useful methods like map, select, reduce, and many more.
In this tutorial, you’ll learn how Enumerable works, what methods are available, and how to use them effectively in your Ruby code.
What is Enumerable?
Enumerable is a mixin module that provides collection classes with traversal, searching, and sorting capabilities. To use Enumerable, your class must define an each method that yields each element in the collection.
Ruby includes Enumerable in core classes like Array, Hash, Range, and Set:
# Arrays include Enumerable
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
# Hashes include Enumerable
{ a: 1, b: 2 }.select { |_k, v| v > 1 } # => { b: 2 }
# Ranges include Enumerable
(1..5).sum # => 15
Transforming with map and flat_map
The map method (also known as collect) transforms each element in a collection and returns a new array with the results:
numbers = [1, 2, 3, 4, 5]
# Square each number
squared = numbers.map { |n| n ** 2 }
# => [1, 4, 9, 16, 25]
# Convert to strings
stringified = numbers.map(&:to_s)
# => ["1", "2", "3", "4", "5"]
If your transformation returns arrays and you want to flatten the result, use flat_map:
words = ["hello", "world"]
# map would return arrays of characters
chars = words.map { |word| word.chars }
# => [["h", "e", "l", "l", "o"], ["w", "o", "r", "l", "d"]]
# flat_map flattens one level
flat_chars = words.flat_map { |word| word.chars }
# => ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]
Filtering with select and reject
The select method (also known as find_all) returns elements that match a condition:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Get even numbers
evens = numbers.select { |n| n.even? }
# => [2, 4, 6, 8, 10]
# Get numbers greater than 5
greater_than_five = numbers.select { |n| n > 5 }
# => [6, 7, 8, 9, 10]
The opposite of select is reject, which returns elements that don’t match:
numbers = [1, 2, 3, 4, 5]
# Remove odd numbers
no_odds = numbers.reject { |n| n.odd? }
# => [2, 4]
You can also use filter_map to combine filtering and transformation in one pass:
numbers = [1, 2, 3, 4, 5]
# Only double even numbers
result = numbers.filter_map { |n| n * 2 if n.even? }
# => [4, 8]
Finding Elements
The find method (also known as detect) returns the first element matching a condition:
numbers = [1, 2, 3, 4, 5]
# Find first even number
first_even = numbers.find { |n| n.even? }
# => 2
# Find returns nil if no match
no_match = numbers.find { |n| n > 100 }
# => nil
Use find_all to get all matches (same as select):
numbers = [1, 2, 3, 4, 5]
all_evens = numbers.find_all { |n| n.even? }
# => [2, 4]
The any?, all?, none?, and one? methods check conditions across the collection:
numbers = [1, 2, 3, 4, 5]
numbers.any? { |n| n > 3 } # => true (at least one > 3)
numbers.all? { |n| n > 0 } # => true (all are > 0)
numbers.none? { |n| n > 10 } # => true (none are > 10)
numbers.one? { |n| n == 3 } # => true (exactly one equals 3)
Aggregating with reduce
The reduce method (also known as inject) accumulates values across a collection:
numbers = [1, 2, 3, 4, 5]
# Sum all numbers
sum = numbers.reduce(0) { |acc, n| acc + n }
# => 15
# Or use the shorthand with :+
sum = numbers.reduce(:+)
# => 15
# Multiply all numbers
product = numbers.reduce(1) { |acc, n| acc * n }
# => 120
You can also use sum directly for simpler cases:
numbers = [1, 2, 3, 4, 5]
numbers.sum # => 15
numbers.sum(100) # => 115 (starts with 100)
Grouping and Counting
Group elements by a criterion using group_by:
words = ["apple", "banana", "cherry", "date"]
by_length = words.group_by { |word| word.length }
# => {5=>["apple"], 6=>["banana", "cherry"], 4=>["date"]}
Count occurrences with tally:
letters = ["a", "b", "a", "c", "b", "a", "d"]
counts = letters.tally
# => {"a"=>3, "b"=>2, "c"=>1, "d"=>1}
Sorting
The sort_by method sorts elements by a computed value:
words = ["apple", "banana", "cherry", "date"]
# Sort by length
by_length = words.sort_by { |word| word.length }
# => ["date", "apple", "banana", "cherry"]
# Sort by multiple criteria
words.sort_by { |w| [w.length, w] }
# => ["date", "apple", "banana", "cherry"]
For descending order, chain reverse:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort.reverse
# => [9, 6, 5, 4, 3, 2, 1, 1]
Combining Collections
The zip method combines elements from multiple arrays:
names = ["alice", "bob", "charlie"]
ages = [25, 30, 35]
zipped = names.zip(ages)
# => [["alice", 25], ["bob", 30], ["charlie", 35]]
# Convert to hash
names.zip(ages).to_h
# => {"alice"=>25, "bob"=>30, "charlie"=>35}
Lazy Enumeration
For large or infinite collections, use lazy to defer evaluation:
# This would hang without lazy!
result = (1..Float::INFINITY)
.lazy
.select { |n| n.even? }
.first(5)
# => [2, 4, 6, 8, 10]
Lazy enumeration is powerful for processing large datasets or working with streams without loading everything into memory.
Enumerable with Custom Objects
You can add Enumerable to your own classes by implementing each:
class Inventory
include Enumerable
def initialize
@items = []
end
def add(item)
@items << item
self
end
def each(&block)
@items.each(&block)
end
end
inventory = Inventory.new
inventory.add("apple").add("banana").add("cherry")
# Now you can use Enumerable methods
inventory.map(&:upcase)
# => ["APPLE", "BANANA", "CHERRY"]
inventory.select { |item| item.start_with?("a") }
# => ["apple"]
When to Use Enumerable
Enumerable methods work best when you need to:
- Transform data: Use
map,flat_mapto convert collections - Filter data: Use
select,reject,findto extract specific elements - Aggregate data: Use
reduce,sum,countto compute totals - Search: Use
any?,all?,none?,one?to check conditions - Group: Use
group_by,tallyto organize data
Avoid using Enumerable methods when you need imperative loops with early exit—consider a simple each with break instead.
Summary
Ruby’s Enumerable module provides a rich set of methods for working with collections. By understanding these methods, you can write more expressive, concise, and Ruby-idiomatic code. The key methods to remember are:
- Transformation:
map,flat_map,filter_map - Filtering:
select,reject,find - Aggregation:
reduce,sum,count - Searching:
any?,all?,none?,one? - Organization:
group_by,tally,sort_by,zip
Practice using these methods in your Ruby code, and you’ll find yourself writing more elegant solutions to common programming problems.