6 Advanced Ruby Loops
Ruby's toolbox has everything you need
Benny Sitbon
(Originally published on Medium)
Over the last few years, I’ve been writing a lot of Ruby. The language has earned a special place in my heart due to its “optimizing for developer happiness” principle.
It’s a language that is easy to read, write, onboard, template, and prototype, among other benefits.
One of its best features is providing helper methods to many commonly-used procedures. Iterating, sorting, filtering, transforming, and others all have multiple helper methods built-in to the language (in the enumerable module).
You can focus on your logic more, and less on writing helper methods that iterate over enumerables.
Not only does this make code a lot more fun to write, but it also makes the code more readable.
These helper methods abstract the details of how the iteration happens, and give the stage to logic itself. Since code is read much more often than it is written, it also makes for more efficient coding.
In an effort to share some “developer happiness”, I’d like to go over several less-known Ruby loops. These will make your code less verbose, easier to read, and quicker to write:
map
each_with_object
each_with_index
partition
select/reject
any?/all?
- Bonus:
find_index
Please note: I’m using an array of numbers in most examples here for simplicity. But all these methods will work both for arrays and hashes. Both are considered enumerables.
map
Useful for creating an array out of an enumerable and applying certain logic for each element.
For example, squaring each value in an array of numbers. So, instead of using each
to iterate and insert the result to another array, you can just use map
.
Code example:
# ok: Instead of using each and appending to a var
def build_array(nums)
result = []
nums.each do |num|
result << calculate(num)
end
result
end
# better: You can just use map
def build_array(nums)
result = nums.map { |num| calculate(num) }
end
# best: Or even shorter.
def build_array(nums)
result = nums.map(&:calculate)
end
# Actually, we can skip this method and just inline `result = nums.map(&:calculate)`.
each_with_object
Useful for creating an object by iterating over an enumerable.
For example, what if we wanted to produce a hash from an array? Or a Person
object from a given hash?
You could also use the reduce
method, but I find its syntax confusing, so I prefer each_with_object
.
Pro tip: Unlike map
, you can add if
s to the block and pick and choose which elements to run the logic on.
Code example:
# ok: you can use an instantiated hash, which isnt pretty
def build_hash(nums)
h = {}
nums.each { |num| h[num] = calculate(num) }
h
end
# better: or you can use each_with_object
def build_hash(nums)
# it receives the object to start with ({}) and accumulates on top of it
nums.each_with_object({}) { |num, res| res[num] = calculate(num) }
end
# bonus: works with any kind of object
def build_array(nums)
# you can replace the accumulator to be an array, or any object really.
nums.each_with_object([]) { |num, res| res << calculate(num) }
end
each_with_index
Useful when, in addition to iterating over an enumerable, you also need to use the index. For example, what if we need to log the current index we’re processing?
Code example:
# ok: Let's print a status log before each iteration
def handle(nums)
num_count = nums.count
# we can use the count to iterate
for i in 0..num_count do
Rails.logger.info("Handling item #{i}/#{num_count}")
do_something(nums[i])
end
end
# better: much more readable
def handle(nums)
nums.each_with_index do |num, i|
Rails.logger.info("Handling item #{i}/#{nums.count}")
do_something(num)
end
end
partition
Imagine you could, in one line, split an array into two arrays, based on a condition. Now stop imagining, you can do exactly that with partition.
Code example:
# ok: let's split an array to an odd array and even array
def split_odds_evens(nums)
odds = []
evens = []
nums.each do |num|
if num.odd?
odds << num
else
evens << num
end
end
[odds, evens]
end
# better: ruby helps you out! So simple, just inline it
def odd_even_short(nums)
nums.partition(&:odd?)
end
select/reject
select
iterates over an enumerable and returns only the elements that answer the given block. rejects
acts the same but opposite, it returns the ones that don’t answer the condition:
For arrays, select
and reject
are available in !
(bang) flavors as well: select!
and reject!
. These will modify the given enumerable (!
is often used to signal a destructive operation), so be careful when using it.
Code example:
### select ###
# ok: get a new array with only odd numbers
def only_odds(nums)
result = []
nums.each do |num|
result << num if num.odd?
end
result
end
# better: shorter and cleaner
def only_odds_object(nums)
nums.each_with_object([]) do |num, arr|
arr << num if num.odd?
end
end
# best: so simple you can just inline it
def only_odds_simple(nums)
nums.select(&:odd?)
end
### reject ###
# exact opposite of `select`
def only_odds(nums)
nums.reject(&:even?)
end
any?/all?
any?
checks that at least one element in an enumerable corresponds to a condition. all?
verifies that all elements fit the bill.
Code example:
### all? ###
# ok: checking if all nums are odd
def all_odds?(nums)
nums.each do |num|
return false if num.even?
end
true
end
# better: so simple you don't even really need a method, just inline it
def easier_all_odds?(nums)
nums.all?(&:odd?)
end
### any? ###
# ok: checking if any of the numbers are greater than 0
def any_greater_than?(nums, x)
nums.each do |num|
return true if num > x
end
return false
end
# better: so simple, it might be better to just write it inline
def easier_any_greater_than?(nums, x)
nums.any? { |num| num > x }
end
Bonus: find_index
Sometimes, you just need to find the index of an element matching a condition.
Code example:
# ok: looking for an index of a specific object - explicit solution
def find_num(nums, x)
nums.each_with_index do |num, i|
return i if num == x
end
nil
end
# better: so simple, just write it inline
def find_num(nums, x)
nums.find_index(num)
end
# this actually works with blocks too. This would work for all object
# just need the right coparer func
def find_obj(array, obj)
array.find_index{ |element| yield(element, obj) }
end
# call it like this:
# find_obj([1,2,3], 2) { |x, y| x == y }
# => 1
To Iterate the Topic One Last Time
- If you need a special way to iterate over enumerables in Ruby, there’s a good chance it already exists. If you use a framework (like Rails), you might have even more methods, as they include gems that expand on the base collection.
- The above helper methods help you write more concise and focused code, therefore making it easier for a human to read.
Thanks for reading!
Upvote
Benny Sitbon

Related Articles