1
0

【Ruby】mapメソッドとindex_byメソッド(初学者)

Last updated at Posted at 2024-01-22

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

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

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

mapメソッド

配列の要素の数だけブロック内で処理を繰り返して、新しい配列を返す。

hoges(配列).map { |hoge| 実行する処理 }

# 実行する処理が複数行に渡る場合
hoges(配列).map do |hoge|
  実行する処理
具体例
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の配列が格納される

この記述方法は&:を使うことでより簡潔に記述することができる

&:を利用
user_ids = all_requests.map(&:user_id)

ただし以下3つの条件をクリアした場合のみ&:を使用することができる
・ブロックの引数が1つであること
・ブロックで呼び出すメソッドに引数がないこと
・ブロック引数に対して、メソッドを呼び出すこと以外の処理がないこと

これまで学習する中で&:という記述はよく見かけましたが、実際どんなやつかさっぱり分からなかったので調べました

&と:

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

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

Symbol#to_proc

リファレンスマニュアル読んだけど、いつもどおりなるほどわからんなので調べる
Procオブジェクト:ブロックをオブジェクト化したProcクラスのインスタンス
Procクラス:ブロックをオブジェクトとして扱うためのクラス

Procオブジェクトはメソッドとして定義されていないコードブロックをcallメソッドで呼び出すことでメソッドのように扱うことができる

Procオブジェクトを使用する場合
say_hello_proc = Proc.new { puts "Hello, world!" }
say_hello_proc.call  # 出力: "Hello, world!"
通常のクラスメソッドを定義する場合
class Greeting
 def say_hello
   puts  "Hello, World!"
  end
end

Procオブジェクトをcallメソッドで呼び出すメリットは
・どこでも定義して使用でき、他のメソッドやProcに渡すことができる
・名前を定義する必要がなく、一時的な使用やコンパクトな記述に適している
デメリットは
・コードが分散するため可読性が低下する
・メソッド名として定義しないため、デバック時にバグの原因が見つけづらくなる
・メソッド呼び出しよりも僅かに呼び出しが遅いことがある

mapメソッドを使ってデータベースから特定の値だけ配列として取得する方法

users = User.all
users.map { |user| user.name }
#または
users.map(&:name)
# => ["Jacob", "Jackson", "Noah", "Lucas", "Sophia", "Chloe", "Abigail", "Harry"] 

index_byメソッド

Railsドキュメントを読んだけどイマイチわからないので試してみる
※RequestオブジェクトにはcreateしたUserのidがuser_idとして一緒に保存されています
※RequestオブジェクトはGroupごとに生成されています

  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) user_ids
[30, 29]
(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) @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>}
 user_ids = all_requests.map(&:user_id)
 # => [30, 29]

この記述でall_requestsからuser_idを抽出し、配列としてuser_idsに格納

@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

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0