DEV Community

Cover image for Be more sophisticated with Enumerable
toooooooooomy
toooooooooomy

Posted on • Updated on

Be more sophisticated with Enumerable

What is Enumerable?

The Enumerable mixin provides collection classes with several traversals and searching methods, and with the ability to sort. The class must provide a method each, which yields successive members of the collection.

https://ruby-doc.org/core-2.7.1/Enumerable.html
Simply, Classes which have each method like Array, Hash, and String have Enumerable methods

Find code which uses each, consider using Enumerable methods

If you see the code which uses each, potentially you can re-write the procedure without using it. Try to look for it if you think up some parts of your codebase.
Furthermore, utilize appropriate Enumerable method makes your code sophisticate. Let's see and learn with the exact examples which I've ever seen.

Usecases

Case 1: select

Before

arr = [1, 2, 3, 4, 5]
new_arr = []
arr.each do |v|
  new_arr << v if v.odd?
end

p new_arr # => [1, 3, 5]
Enter fullscreen mode Exit fullscreen mode

After

new_arr = arr.select(&:odd?)

p new_arr # => [1, 3, 5]
Enter fullscreen mode Exit fullscreen mode

Case 2: map

Before

arr = [1, 2, 3, 4, 5]
new_arr = []
arr.each do |v|
  new_arr << v * 2
end

p new_arr # => [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

After

new_arr = arr.map { |v| v * 2 }

p new_arr # => [2, 4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

Case 3: inject

Before

arr = [1, 2, 3, 4, 5]
sum = 0
arr.each do |v|
  sum += v
end

p sum # => 15
Enter fullscreen mode Exit fullscreen mode

After

arr = [1, 2, 3, 4, 5]
sum = arr.inject(:+)

p sum # => 15
Enter fullscreen mode Exit fullscreen mode

Case 4: any?

Prerequisite

booking_statuses are defined as below and intend to validate the status transition

booking_statuses = {
  pending: 0,
  payment_requested: 1,
  paid: 2,
  cancelled: 3
}
Enter fullscreen mode Exit fullscreen mode

Before


def validate_booking_transition(passed_status)
  if passed_status == booking_statuses[:cancelled]
    allowed = [
      booking_statuses[:pending],
      booking_statuses[:payment_requested],
      booking_statuses[:paid]
    ].include?(passed_status)
  elsif ...
  .
  .
  .
end
Enter fullscreen mode Exit fullscreen mode

After

def validate_booking_transition(passed_status)
  if passed_status == booking_statuses[:cancelled]
    allowed = %i(pending payment_requested paid).any? do |v|
      passed_status == booking_statuses[v]
    end
  elsif ...
  .
  .
  .
end
Enter fullscreen mode Exit fullscreen mode

Case 5: group_by

Before

arr = [{code: 'a', val: 1}, {code: 'a', val: 2}, {code: 'b', val: 3}, {code: 'b', val: 4}]
new_hash = {}
arr.each do |hash|
  k = hash[:code]
  new_hash[k] = [] if new_hash[k].nil?

  new_hash[k] << hash[:val]
end

p new_hash #=> {"a"=>[1, 2], "b"=>[3, 4]}
Enter fullscreen mode Exit fullscreen mode

After

new_hash = arr.group_by { |h| h[:code] }.transform_values { |grouped_arr| grouped_arr.map { |h| h[:val] } }

p new_hash #=> {"a"=>[1, 2], "b"=>[3, 4]}
Enter fullscreen mode Exit fullscreen mode

Consideration

Look at each use-case. Case 1-3 are general Enumerable methods' introduction.
Case 4 shows us utilizing appropriate Enumerable methods would be able to express what we intend to.
So, what can say about Case 5? Does it look smarter than before? Or it became more complex?
Let's see more details about each case.
In the loop,

  • Case 1: Extract params which fit the condition
  • Case 2: Multiply 2 to each param
  • Case 3: Sum up the params
  • Case 4: Check there are any params which fit the condition

From that, we can say they proceed 1 procedure within the loop.
The difference of Case 5 compared with others, is it does several procedures within the iteration.
They are

  • Generate a hash which has code as key
  • Generate val array per hash key

Furthermore, it executes map within transform_values method. It made the situation worse -- yes, from the Calculate order aspect, it generates a dual-loop from a single loop.
So that we can say, if we should handle several procedures within a single iteration, each could have initiative from both Calculation Order and Readability aspects.

Re-think the pros of using Enumerable methods

We can describe our intention more clearly by appropriate Enumerable methods rather than each.
And in many cases, readability and maintainability are the most important policy. So why not using it?
And if you aren't familiar with functional programming, it even could be a good opportunity to think about side effects.
But, as I demonstrate by Case 5, each could be the best approach after all. Make sure you do not generate nested iterations by refactoring.

Conclusion

Enumerable makes your programming fun. Enjoy!
In Japanese

Top comments (0)