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.
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:
include Enumerableto bring the
Enumerablemodule into your class
- Add a method called
eachthat, in turn, calls the method on
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.
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
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
- 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!
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.