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 Enumerable
to bring theEnumerable
module into your class - Add a method called
each
that, 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
each
that 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.