Page Content

Tutorials

Ruby Enumerable Module: Essential Methods Every Developer

Ruby Enumerable Module

Among the most essential and potent modules in Ruby is the Enumerable module. As a mixin, it gives any class that uses it a wide range of practical iteration and collection manipulation techniques. The Enumerable module in Ruby provides a set of methods for traversing, searching, and manipulating collections of objects. It is a mixin module, meaning it is intended to be included in other classes, imparting its functionality to those classes. 

Core Functionality and Implementation

Arrays and hashes are examples of collections that may be traversed, sorted, searched, classified, and transformed using the Enumerable module.

The Requirement of Enumerable Module

The amazing characteristic of the Enumerable module is that it provides about 22 features related to iteration with little effort.

A class that wants to use the Enumerable module’s rich features needs follow one set of rules: it must contain Enumerable and explicitly implement the each instance method.

Every method provided by the host class is the foundation upon which the Enumerable module’s methods such as detect and inject are created. The corresponding block must receive successive members of the collection from each procedure.

Example of Implementing Enumerable

Methods like reduce, select, and map are automatically added to a custom class that implements each and contains Enumerable.

This is an example of code for a custom class that has Enumerable methods:

class NaturalNumbers
  include Enumerable # 1. Include the Enumerable module

  def initialize(upper_limit)
    @upper_limit = upper_limit
  end

  # 2. Implement the 'each' method
  def each(&block)
    # Natural numbers typically start at 1, but 0.upto(6) returns 0, 1, 2, 3, 4, 5, 6
    # If you meant 1, you should use 1.upto(@upper_limit).
    # Assuming you want 0 through @upper_limit based on your 'each' implementation:
    0.upto(@upper_limit).each(&block)
  end
end

n = NaturalNumbers.new(6)

# The collection is [0, 1, 2, 3, 4, 5, 6]

# Now 'n' can use Enumerable methods:
# n.reduce(:+)
# Sum of 0 + 1 + 2 + 3 + 4 + 5 + 6 = 21
puts n.reduce(:+)                   # => 21

# n.select(&:even?)
# Selects numbers that are divisible by 2: 0, 2, 4, 6
puts n.select(&:even?).inspect      # => [0, 2, 4, 6]

# n.map { |number| number ** 2 }
# Squares each number: 0**2, 1**2, 2**2, 3**2, 4**2, 5**2, 6**2
# 0, 1, 4, 9, 16, 25, 36
puts n.map { |number| number ** 2 }.inspect # => [0, 1, 4, 9, 16, 25, 36]

Output

21
[0, 2, 4, 6]
[0, 1, 4, 9, 16, 25, 36]

Likewise, by defining each iterator and adding Enumerable, the Sequence class that we covered in our earlier discussion is made an enumerable class:

class Sequence
  # This is an enumerable class; it defines an each iterator below.
  include Enumerable
  
  # 1. ADDED: Initialize method to set the range and step
  def initialize(from, to, by)
    @from = from
    @to = to
    @by = by
  end
  
  # This is the iterator required by the Enumerable module
  def each
    x = @from
    while x <= @to
      yield x
      x += @by
    end
  end
end  

# 2. ADDED: Instantiate the class and call an Enumerable method to produce output

# Example 1: Create a sequence from 10 to 30, stepping by 5
s1 = Sequence.new(10, 30, 5)

# Use 'map' (an Enumerable method) to collect the sequence values
puts "Sequence 1 (10 to 30, step 5):"
puts s1.map { |x| x }.inspect # => [10, 15, 20, 25, 30]

# Example 2: Find the count of elements greater than 15
s2 = Sequence.new(10, 50, 8)
puts "\nSequence 2 (10 to 50, step 8):"
puts "Elements: " + s2.map { |x| x }.inspect
puts "Count of elements > 15: " + s2.count { |x| x > 15 }.to_s
# The sequence is [10, 18, 26, 34, 42, 50]
# Count of elements > 15 is 5 (18, 26, 34, 42, 50)

Output

Sequence 1 (10 to 30, step 5):
[10, 15, 20, 25, 30]

Sequence 2 (10 to 50, step 8):
Elements: [10, 18, 26, 34, 42, 50]
Count of elements > 15: 5

Classes that Include Enumerable

Within the Enumerable module, a large number of standard Ruby classes often called collection classes mix. Famous instances consist of:

  • Array
  • Hash
  • Range
  • IO
  • Set (from the standard library)
  • Dir
  • String (while generally String methods are not dependent on each, they can generate enumerators in some situations)
  • Struct

Key Methods Provided by Enumerable

There are many methods defined by the Enumerable module. Several of the most popular techniques are sort_by, find_all, inject, and collect.

Each and its Variants

In the fundamental iterator, every element of the collection is passed to the corresponding block.

MethodDescriptionCode ExampleSource
eachIterates over every element of a collection.`.each {x
each_with_indexYields the element and an integer index/line number.`(5..7).each_with_index {x,i

Transformation and Selection

Based on the outcomes, these methods return a new array after executing the related block.

MethodDescriptionCode ExampleResultSource
collect (or map)Executes the block for each element and collects the block’s return values into a new array.`.collect {xx*x}`
select (or find_all)Returns an array of elements for which the block returns a value that is neither false nor nil.`(1..10).select {xx%2 == 0}`
rejectReturns an array of elements for which the block returns nil or false.`(1..10).reject {xx%2 == 0}`

Accumulation ()

A value is accumulated over iterations by the inject method, commonly referred to as reduce. The accumulated value (sum or memo) and the current collection element are the two arguments used to call the block. For the subsequent iteration, the block’s return value serves as the accumulator.

MethodDescriptionCode ExampleResultSource
injectCombines the elements by applying the block to an accumulator and each element.data = `sum = data.inject {sum, xsum + x }`

Searching and Testing

MethodDescriptionCode ExampleSource
detect (or find)Returns the first element for which the block returns true.(Implicit in source listing)
all?Returns true if the block returns true for all elements.(Implicit in source listing)
any?Returns true if the block returns true for any element.(Implicit in source listing)

Enumerable and Enumerators

Many standard iterators and Enumerable methods in Ruby 1.9 return an Enumerator object when they are called without a corresponding code block. Enumerable objects are enumerator objects.

Method chaining is made possible by this behavior, allowing operations to be functionally composed.

Code Example (Returning an Enumerator):

# The `downto` iterator returns an Enumerator if no block is supplied
enumerator = 10.downto(1)
# puts enumerator.inspect # => #<Enumerator: 10:downto(1)>

# The Enumerator itself can then use Enumerable methods like select
# ADDED: The 'puts' command to display the result
puts enumerator.select {|x| x%2==0}.inspect

Output

[10, 8, 6, 4, 2]

When no block is supplied, an iteration method such as each is created to return an Enumerator, allowing for intricate chained operations:

class MyIterator
  # The iterator method that yields symbols
  def each
    # This is the key line: returns an Enumerator if no block is supplied
    return enum_for :each unless block_given?
    
    yield :x
    yield :y
    yield :z
  end
end

# 1. Instantiate the class
iterator = MyIterator.new

# 2. Call the chain of Enumerator/Enumerable methods and use 'puts' to display the result
puts iterator.each.drop(2).map(&:upcase).first

Output

Z

You can also read Exception In Ruby: Custom Classes And The Hierarchy

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index