[RUBY] More usable Enumerable for beginners

Introduction

In this article, I'll write about this and that related to Enumerable, which I often point out as actually doing pull request reviews in business. Not all methods are covered, so don't be afraid. Click here for English version

What is Enumerable?

Mix-ins for repeating classes. All the methods in this module are defined using each, so each must be defined in the included class.

https://docs.ruby-lang.org/ja/latest/class/Enumerable.html ... apparently ... In other words, classes that normally use each (eg Array, Hash, String) include this Enumerable module.

If you find each, use it

If you find each process, you might do the same without using each. Let's stop and look for a way to call without using each. Also, depending on what you want to do, using a different Enumerable method can be cleaner.

Practice Enumerable

Aside from the difficult things, let's take a look at the things that I often point out. For things that are more than business, I will try to give meaning to variables.

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]

After

new_arr = arr.select(&:odd?)

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

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]

After

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

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

Case 3: inject

Before

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

p sum # => 15

After

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

p sum # => 15

Case 4: any?

Premise

The reservation status is defined as follows. I want to validate whether the status change is correct

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

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

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

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

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

Consideration

Let's consider each one a little. Regarding Cases 1 to 3, it is a pattern that the processing that is done in common each, the convenient method, is used. It's almost an introduction to Enumerable. Case 4 is about the usage of Enumerable, and there is a pattern that makes redundant things more concise by using different Enumerable methods depending on what you want to do. (A little business logic element is added to make the image easier) What about Case 5? In this regard, simply spend one line and feel refreshed! I feel like it, but I think it's hard to understand. If you take a closer look, in Cases 1 to 4, put it in the iterative process,

While doing one thing like that, in Case 5

If you take a closer look, you're running more maps inside the transform_values block. At first glance, it seems to be simple to write, and you have created a double loop. If you need to do multiple things in one iteration like this, using each may be easier to understand and may have advantages in terms of processing speed.

What are the benefits of using the Enumerable method correctly?

I think that the most important index for writing code in many development sites is readability and ease of maintenance. While the each method is useful, it doesn't make sense in itself (it's basically just a loop), so sometimes it's possible that the reader doesn't know what the code writer intended. Proper use of the Enumerable method will clarify the intent of the writer and facilitate future code changes. From a perspective other than readability, I think you will be able to be more clearly aware of the effects and side effects. However, it seems to be long, so I will omit it here. However, as shown in Case 5, there may be a pattern that it is better to use each (especially if you are using the Enumerable method in the Enumerable method), so be careful. Let's do it.

At the end

What do you think. This time I briefly explained the Enumerable method with an example. Let's get used to it and write code more comfortably;)

Recommended Posts

More usable Enumerable for beginners
Scraping for beginners (Ruby)
Java debug execution [for Java beginners]
[Java] Basic statement for beginners
[For super beginners] DBUnit super introduction
(For beginners) [Rails] Install Devise
[For super beginners] Ant super introduction
Java for beginners, data hiding
[For super beginners] Maven super introduction
Java application for beginners: stream
Binary Tree Memorandum for Beginners (Part 1)
[For beginners] Summary of java constructor
Rock-paper-scissors game for beginners in Java
Environment construction with Docker for beginners
Java for beginners, expressions and operators 1
[For beginners] Run Selenium in Java
Links for creating Android apps (for beginners)
Java for beginners, expressions and operators 2
[Folio LSP] Roughly Docker (for beginners)
[For super beginners] Struts2 Super Primer --2018 Edition
Object-oriented course for beginners sent by beginners
Starting heroku through heroku CLI (for beginners)
Recommended learning method for programming beginners
[For Java beginners] About exception handling
Classes and instances Java for beginners