【Ruby】mapメソッドとindex_byメソッド（初学者）

私は現在、未経験からのエンジニア転職に向けてプログラミングスクールで学習をしている、いしかわと申します。

`mapメソッド`と`index_byメソッド`について理解を深めたく学習した内容をアウトプットとしてこちらの記事にしました。
どなたかの参考になれば幸いです。

:::note warn
プログラミング初学者なので、内容に誤り等ある可能性があります
誤りがありましたら教えてくださると幸いです
:::

# mapメソッド
配列の要素の数だけブロック内で処理を繰り返して、新しい配列を返す。
```ruby
hoges(配列).map { |hoge| 実行する処理 }

# 実行する処理が複数行に渡る場合
hoges(配列).map do |hoge|
  実行する処理
```
```ruby:具体例
user_ids = all_requests.map { |request| request.user_id }
user_ids = [1, 2, 3, 4, 5, 6, ..... , 100]
# all_requestsに1~100のuser_idが含まれている場合, このように記述することでuser_idsにall_requestsに含まれるuser_idの配列が格納される
```
この記述方法は`&:`を使うことでより簡潔に記述することができる
```ruby:&:を利用
user_ids = all_requests.map(&:user_id)
```
ただし以下3つの条件をクリアした場合のみ`&:`を使用することができる
・ブロックの引数が1つであること
・ブロックで呼び出すメソッドに引数がないこと
・ブロック引数に対して、メソッドを呼び出すこと以外の処理がないこと

これまで学習する中で`&:`という記述はよく見かけましたが、実際どんなやつかさっぱり分からなかったので調べました
### &と:
~~驚いたのが`「&:」`この記法そのものには**名前がない**ということ。&.(ぼっち演算子)にはあるのにね。~~
そもそも組み合わさったものではなく、それぞれ独立した`&`と`:`であり
`&:`は
・`&(アンパサンド)` -> ~~ブロックをProcオブジェクトに変換する演算子~~`Proc`オブジェクトをブロックとしてメソッドに渡す演算子
・`:（シンボル）` -> Rubyのシンボル。シンボルは変更不可能な文字列のようなもので、メソッド名やキーに使用される
上記2つに分けることができ、~~`&:`とすることで~~、Procオブジェクトをブロックとして扱うことが可能になります

3/6追記
@scivolaさんからご指摘いただいた内容について修正しました。
`&:`は`&`と`:`が組み合わさっているわけではありませんでした。
それぞれが独立して働いています。
よって`&:`に名前が名称がないのではなく、それぞれが独立したものであるため名称がないだけでした。
また`&`は「ブロックをProcオブジェクトに変換する演算子」ではなく「**Procオブジェクトをブロックとしてメソッドに渡す演算子**」の誤りでした！
@scivolaさんありがとうございます！


### Symbol#to_proc
[リファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/method/Symbol/i/to_proc.html)読んだけど、いつもどおり**なるほどわからん**なので調べる
`Procオブジェクト:`ブロックをオブジェクト化したProcクラスのインスタンス
`Procクラス:`ブロックをオブジェクトとして扱うためのクラス

`Procオブジェクト`はメソッドとして定義されていないコードブロックをcallメソッドで呼び出すことでメソッドのように扱うことができる
```ruby:Procオブジェクトを使用する場合
say_hello_proc = Proc.new { puts "Hello, world!" }
say_hello_proc.call  # 出力: "Hello, world!"
```
```ruby:通常のクラスメソッドを定義する場合
class Greeting
 def say_hello
   puts  "Hello, World!"
  end
end
```
Procオブジェクトを`callメソッド`で呼び出すメリットは
・どこでも定義して使用でき、他のメソッドやProcに渡すことができる
・名前を定義する必要がなく、一時的な使用やコンパクトな記述に適している
デメリットは
・コードが分散するため可読性が低下する
・メソッド名として定義しないため、デバック時にバグの原因が見つけづらくなる
・メソッド呼び出しよりも僅かに呼び出しが遅いことがある

## mapメソッドを使ってデータベースから特定の値だけ配列として取得する方法
```ruby
users = User.all
users.map { |user| user.name }
#または
users.map(&:name)
# => ["Jacob", "Jackson", "Noah", "Lucas", "Sophia", "Chloe", "Abigail", "Harry"] 
```
# index_byメソッド
[Railsドキュメント](https://railsdoc.com/page/ruby_convenience_methods)を読んだけどイマイチわからないので試してみる
※RequestオブジェクトにはcreateしたUserのidが`user_id`として一緒に保存されています
※RequestオブジェクトはGroupごとに生成されています


```ruby
  def index
    all_requests = Request.includes(:user).where(group_id: params[:group_id]).order(updated_at: :desc)

    user_ids = all_requests.map(&:user_id)
    @users = User.where(id: user_ids).index_by(&:id)
    binding.b
  end
```
こんな感じでデバッグしてみると
```ruby
(ruby) user_ids
[30, 29]
```
```ruby
(ruby) User.where(id: user_ids)
  CACHE User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2)  [["id", 30], ["id", 29]]
  ↳ app/controllers/requests_controller.rb:20:in `completed'
[#<User id: 29, email: "hogehoge@example.com", created_at: "2023-12-02 04:51:56.263247000 +0900", updated_at: "2023-12-02 04:51:56.263247000 +0900", provider: nil, uid: nil>,
 #<User id: 30, email: "fugafuga@example.com", created_at: "2023-12-02 04:53:53.895742000 +0900", updated_at: "2023-12-02 04:53:53.895742000 +0900", provider: nil, uid: nil>]
```

```ruby
(ruby) @users
{29=>
  #<User id: 29, email: "hogehoge@example.com", created_at: "2023-12-02 04:51:56.263247000 +0900", updated_at: "2023-12-02 04:51:56.263247000 +0900", provider: nil, uid: nil>,
 30=>
  #<User id: 30, email: "fugafuga@example.com", created_at: "2023-12-02 04:53:53.895742000 +0900", updated_at: "2023-12-02 04:53:53.895742000 +0900", provider: nil, uid: nil>}
```
```ruby
 user_ids = all_requests.map(&:user_id)
 # => [30, 29]
```
この記述で`all_requests`から`user_id`を抽出し、配列として`user_ids`に格納
```ruby
@users = User.where(id: user_ids).index_by(&:id)
# =>
{29=>
  #<User id: 29, email: "hogehoge@example.com", created_at: "2023-12-02 04:51:56.263247000 +0900", updated_at: "2023-12-02 04:51:56.263247000 +0900", provider: nil, uid: nil>,
 30=>
  #<User id: 30, email: "fugafuga@example.com", created_at: "2023-12-02 04:53:53.895742000 +0900", updated_at: "2023-12-02 04:53:53.895742000 +0900", provider: nil, uid: nil>}
```
`User.where(id: user_ids)`で`user_ids`に格納された`user_id`の値とUserクラスを照会。この段階で返り値のUserオブジェクトは配列に格納されている
`.index_by(&:id)`で配列で格納されているUserオブジェクトを`{id(キー)=>value}`に変換している

`index_by(&:id)`は配列を`id`要素をキーとし、その他の情報をvalueとするハッシュに変換している

つまり`index_by(:&id)`メソッドは`User.where(id: user_ids)`によって格納された配列（ブロック, Procオブジェクト）に対して`&:id`を使うことによって`idメソッド`をブロックごとに走らせ`id`をキーとしたハッシュに変換している

# 参考記事
https://qiita.com/tatsu0209/items/ec96974b536f2d685928
https://qiita.com/s_tatsuki/items/869af9c0c33d9d650f3f
https://pikawaka.com/ruby/map
https://zenn.dev/keyproducts/articles/e8e3c8aca68f3d