Share This Article
It’s common to need to iterate over a collection of objects in Ruby. For example, you have an array of User objects, and you need to go over each one of them and execute some code.
You can use each method in some cases, but you need a more sophisticated solution in others.
Here’s an example of how you could use each
to print out the name of all the users:
class User
attr_reader :name
def initialize(name)
@name = name
end
end
users = [User.new("John"), User.new("Jane"), User.new("Joe")]
users.each do | user|
puts user.name
end
But what if you need to iterate over a more complex data structure?
For example, you have a tree data structure and must print out all the nodes in the tree.
The each
method is not going to work in this case.
You need a more sophisticated solution that allows you to write code that iterates over complex data structures.
One of the solutions to this problem is the iterator pattern.
What is the iterator pattern?
The iterator pattern is a design pattern that allows you to iterate over a set of objects without specifying the underlying implementation.
The iterator pattern is often used in conjunction with the composite pattern.
Here’s an example of how you could use the iterator pattern with the composite pattern to print out a tree data structure:
class Node
attr_reader :value
def initialize(value)
@value = value
end
end
class Tree
def initialize(root)
@root = root
end
def each
Enumerator.new do |yielder|
yielder.yield @root
end
end
end
tree = Tree.new(Node.new("root"))
tree.each do |node|
puts node.value
end
Note: The generator used in this example is not available in older versions of Ruby, so if you need to support older versions, you will need to use a different solution.
There are other ways to implement the iterator pattern in Ruby.
For example, you could use the Enumerable
module to implement the iterator pattern.
Here’s an example of how you could use the Enumerable
module to print out all the nodes in a tree data structure:
class Node
attr_reader :value, :nodes
def initialize(value, nodes = [])
@value = value
@nodes = nodes
end
def each
nodes.each { |node| yield node }
end
end
root = Node.new("root", [
Node.new("child1"),
Node.new("child2")
])
root.each do |node|
puts node.value
end
Iterating non-collection objects
But what if you want to iterate over an object that is not a collection?
For example, you have a User object and want to iterate over all the attributes of the User object.
You could use the InstanceVariableIterator
class from the Releasy
gem to iterate over an object’s instance variables.
Here’s an example of how you could use the InstanceVariableIterator
class to print out all the instance variables of a user object:
require "releasy"
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
user = User.new("John", 20)
iterator = Releasy::InstanceVariableIterator.new(user)
iterator.each do |instance_variable|
puts "#{instance_variable.name}: #{instance_variable.value}"
end
As you can see, using the iterator pattern, you can iterate over objects that are not collections.
Summary
In this article, you have learned about the iterator pattern and how you can use it to iterate over collections and non-collections. Make sure you check out the Releasy gem if you need to iterate over an object’s instance variables.