Be more sophisticated with Enumerable
Tags: ruby
Published: 2020-09-09 03:17:09

# 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

```ruby
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]
```

### After

```ruby
new_arr = arr.select(&:odd?)

p new_arr # => [1, 3, 5]
```

## Case 2: map

### Before

```ruby
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]
```

### After

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

p new_arr # => [2, 4, 6, 8, 10]
```

## Case 3: inject

### Before

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

p sum # => 15
```

### After

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

p sum # => 15
```

## Case 4: any?
### Prerequisite
booking_statuses are defined as below and intend to validate the status transition

```ruby
booking_statuses = {
  pending: 0,
  payment_requested: 1,
  paid: 2,
  cancelled: 3
}
```

### Before

```ruby

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
```

### After

``` ruby
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
```

## Case 5: group_by

#### Before

```ruby
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]}
```

#### After

```ruby
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]}
```

# 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](https://qiita.com/tooooooooomy/items/a0566982022983d0305e)