【Rails】pluckとmapの違いは?使い分けについてまとめてみた

Ruby on Rails

こんにちは!ジョンです。
今回はRailsの便利メソッドのひとつ、 mappluck の使い分け・違いについて紹介記事を書いてみようと思います。
Railsエンジニアなら1度は見たことあるであろうこのメソッドですが、この記事で更に興味を持っていただけると嬉しいです!

mapについて

簡単に言えば、オブジェクトに対して処理を行い、Arrayにまとめるメソッドです。
例えば、

Fruitテーブル

idname
1りんご
2バナナ
3みかん
4メロン
5スイカ
Fruit.all.map do |item|
  fruit.name
end
=> [ "りんご", "バナナ", "みかん", "メロン", "スイカ" ]

余談

Fruit.all.map(&:name)

と書くと1行でまとめられます。

pluckについて

Rails(ActiveRecord)に用意されている、指定したカラムのレコードの配列を取得するメソッドです。
上記の例をそのまま使うのであれば、

Fruit.all.pluck(:name)
=> [ "りんご", "バナナ", "みかん", "メロン", "スイカ" ]

となります。

何が違うの?

pluck のほうが、テーブルから特定のカラムのみを取得する分、メモリ使用率が抑えられ、同時に処理時間も節約できます。

実験してみる

試しに、300,000件のレコードが入ったテーブルを適当に用意しました。
ここについてはGimeiを使って適当に流し込んだだけなので割愛…

Company.count
=> 300000

そして、そのテーブルに対して、 mappluck で name だけを取得した際のベンチマークを計測。

Benchmark.bm do |x|
  x.report { Company.all.map(&:name) }
  x.report { Company.all.pluck(:name) }
end
-----------
      user     system      total        real
  1.876591   0.033513   1.910104 (  2.718245)
  0.117722   0.025000   0.142722 (  0.545463)

map のほうが pluck の10倍以上かかってますね…

ならもう全部 pluck を使えば良くない?

じゃぁ全てにおいて pluck を使えば良いかと言うと、そうではないのが面白いところです。
上記ではテーブルから直接データを取得しましたが、先にインスタンスを作っていた場合はどうなるでしょう。

# 先にインスタンスを作る
companies = Company.limit(1000)

# インスタンスに対して同様にmapとpluckを試す
Benchmark.bm do |x|
  x.report { 1000.times { companies.map(&:name) } }
  x.report { 1000.times { companies.pluck(:name) } }
end
-----------
       user     system      total        real
   0.198063   0.000000   0.198063 (  0.198079)
   0.284302   0.000000   0.284302 (  0.284310)

先ほどの結果とは一転、pluck のほうが時間がかかっています。
これは何故かを調べてみると、 pluck は毎度SQLを発行しているため、上記のコードの場合、SQLが1000回発行されている訳ですね。
一方で map は、インスタンスに対してはSQLを発行しないため、処理が早くなっている、という仕掛けでした。

まとめ

  • テーブルから直接値を取得するなら pluck を使う
  • インスタンス化したものに対して取得するのであれば、 pluck より map を使う

何も考えず、適当に pluck ばかり使っていると、うっかりN+1問題を発生させてしまったりすることもあるかもしれないですね…!

コメント

タイトルとURLをコピーしました