One of the cool things in Ruby is to be able to pass a block to a method or go through all the elements in a class (such as an array or hash) and do something as you step through each element. This is a short post on how to add an each method to your class.
Delegating Enumeration
If enumeration is already provided by a member of your class, the process is really simple.
Let’s take a class (called EachOne) like this, for simplicity:
1
2
3
4
5
class EachOne
def initialize
@elements = [1, 44, 5, 7, 2, 6, 7]
end
end
So, this has an array called elements. We could, of course, make elements available to the outside world by using attr_reader :elements so that we can iterate on that directly but this exposes the internal implementation. The better way is to add a method called each for objects of EachOne but get that method to instead iterate through the array.
Since we want it to make it enumerable, we need to do 2 things:
- Add
include Enumerableto bring theEnumerablemodule into your class - Add a method called
eachthat, in turn, calls the method onelements
So, this becomes:
1
2
3
4
5
6
7
8
9
10
class EachOne
include Enumerable
def initialize
@elements = [1, 44, 5, 7, 2, 6, 7]
end
def each(&block)
@elements.each(&block)
end
end
For many cases, this will be enough. We receive &block as a parameter on the each method and we just call the each method on the elements array, passing it the &block as an argument.
Implementing Enumeration
We won’t implement a custom data structure in this post but I strongly recommend reading https://blog.appsignal.com/2018/05/29/ruby-magic-enumerable-and-enumerator.html which has a well-written post on this topic.
Using the ideas from that post, we’ll implement the enumeration on the elements array as custom code rather than just passing the block to the each method on elements.
What we want to achieve is this:
- Have a method called
eachthat takes the block as a parameter - The method goes through all the elements and calls the block (using
call) passing each item to it
In this case, the each method becomes as below.
1
2
3
4
5
def each(&block)
@elements.each do |x|
block.call(x)
end
end
This works for the cases where you want to pass a block but we need to return an Enumerator instance when each is called without a block. As explained in the AppSignal post, we need to:
- Wrap the object in an enumerator by calling
to_enum(:each)on it - Add a check using
block_given?to decide whether we should enumerate and call the block, or return the object wrapped in an enumerator
The code then becomes as below.
1
2
3
4
5
6
7
8
9
def each(&block)
if block_given?
@arr.each do |x|
block.call(x)
end
else
to_enum(:each)
end
end
I wrote this up so that I could remember how this is done. If it helps someone else, that’s great!
Additional reading
As mentioned above, I recommend reading this page on Ruby’s magical Enumerable module that I also take reference from. On blocks in general, the bootrails post on blocks, procs and lambda is an easy-to-understand post.