【Rails入門説明書】pluckについて解説
はじめに
Railsアプリケーションの処理の中心は、ActiveRecordによるテーブル操作です。
そのため、ActiveRecordには便利なメソッドが多数搭載されており、少ないコードで多彩な処理を行うことができるようになっています。
しかし、ActiveRecordとしてではなく、素のデータだけが欲しいことも少なくありません。
そんなときに便利なのが、pluckメソッドです。
ここでは、そんなpluckメソッドについて詳しく解説します。
よく似たことができるメソッドであるmapメソッドとの比較や、使いどころも説明しますので、ぜひお読みください。
pluckとは
pluckメソッドは、テーブルから任意のカラム(列)のデータを取得して、配列として返してくれるメソッドです。
具体的に確認してみましょう。
環境構築
もっとも簡単に環境作成できますので、scaffoldを用います。
[bash]rails new pluck_test
cd pluck_test
rails generate scaffold User name:string age:integer
rails generate scaffold Item user_id:integer title:string price:integer
rake db:migrate
rails c
User.create(name:”Ryosuke”,age:18)
User.create(name:”Daigo”,age:23)
User.create(name:”Hiroshi”,age:35)
Item.create(user_id:3,title:”pen”,price:1000)
Item.create(user_id:2,title:”note”,price:100)
Item.create(user_id:2,title:”book”,price:1500)
exit
[/bash]
(app/models/users.rb)
class User < ApplicationRecord
has_many :items
end
(app/models/items.rb)
class Item < ApplicationRecord
belongs_to :user
end
任意のカラム(列)のデータを取得
今回は、ブラウザではなく、rails console(コマンドプロンプト)で確認します。「rails c」でrails consoleを起動してください。
例えば、氏名の入っているname列のデータを取得してみましょう。
[bash]irb(main):001:0> User.pluck(:name)
(0.0ms) SELECT “users”.”name” FROM “users”
=> [“Ryosuke”, “Daigo”, “Hiroshi”]
[/bash]
このように、カラム名を指定してpluckメソッドを実行するだけで、配列を取得できるのです。
また、複数のカラムを指定して取得することもできます。
[bash]irb(main):002:0> User.pluck(:name, :age)
(0.0ms) SELECT “users”.”name”, “users”.”age” FROM “users”
=> [[“Ryosuke”, 18], [“Daigo”, 23], [“Hiroshi”, 35]]
[/bash]
複数カラムを指定した場合は、配列の配列(2次元配列)として取得できます。(ただし、複数カラムを指定できるのは、Rails4以降です)
このように、pluckメソッドを使うことで、任意のカラムのデータを配列として取得できるのです。
また、pluckを使うことで、高速にかつ少ないメモリ消費でデータを取得できることも分かっています。そのため、純粋にテーブルのデータを取得するだけであれば、pluckメソッドを使うことを推奨されているのです。
構文
では、改めてpluckメソッドの構文を説明しましょう。
モデル.pluck(カラム名, ..)
モデルの部分には、前述のようなモデルそのもの以外にも、whereメソッドで絞り込んだActiveRecordオブジェクトも指定できます。
そのため、以下のように、絞り込みやソートなどの面倒な処理をActiveRecordで行ったうえで、pluckを使ってデータを取得するというのも良いでしょう。
[bash]irb(main):003:0> User.where([“age < ?”, 30]).pluck(:name, :age)
(0.0ms) SELECT “users”.”name”, “users”.”age” FROM “users” WHERE (age < 30)
=> [[“Ryosuke”, 18], [“Daigo”, 23]]
[/bash]
“未経験”でもたった1ヶ月で営業からエンジニアとして転職!『WebCamp』受講者インタビュー
mapメソッド
じつは、pluckメソッドと同じようにカラムの情報を配列で取得できるメソッドに、mapメソッドがあります。
mapメソッドを使えば、前項でpluckメソッドを使って行っていることが、それほど複雑になることなく実現できるのです。
[bash]irb(main):004:0> User.all.map(&:name)
User Load (0.0ms) SELECT “users”.* FROM “users”
=> [“Ryosuke”, “Daigo”, “Hiroshi”]
[/bash]
irb(main):005:0> User.all.map{|user| [user.name, user.age]}
User Load (9.6ms) SELECT “users”.* FROM “users”
=> [[“Ryosuke”, 18], [“Daigo”, 23], [“Hiroshi”, 35]]
[bash]
irb(main):006:0> User.where([“age < ?”, 30]).map{|user| [user.name, user.age]}
User Load (0.0ms) SELECT “users”.* FROM “users” WHERE (age < 30)
=> [[“Ryosuke”, 18], [“Daigo”, 23]]
[/bash]
pluckメソッドとmapメソッドの違い
前述のように、mapメソッドを使えば、pluckメソッドで得られる結果と同様の結果を得ることができます。つまり、この2つは、結果だけ見ると同じと言えます。
しかし、明確に違っており、多くの参考書や解説サイトでpluckメソッドを推奨されています。
それは、pluckメソッドとmapメソッドに、次のような2つの大きな違いがあるからです。
・処理速度が違う
・メモリの使用量が違う
それぞれ、解説しましょう。
処理速度が違う
一説によると、pluckメソッドはmapメソッドよりも4倍ほど早いと言われています。
今回のような小さなテーブルであれば感じないかもしれませんが、商用サイトなどのように数千、数万単位のレコードを持つテーブルからデータを取得する場合、この速度の差はとても大きな差になるでしょう。
例えば、商品の一覧を表示するのに1秒のところが4秒もかかってしまっては、ページを閉じてしまうユーザーの数は全く違うのではないでしょうか。テーブルからのデータ取得の速度は、それほどユーザビリティに影響するのです。
実際のところ、今回のテスト環境のような小さなテーブルでも、この差が現れています。ここまでに紹介した実行結果の中の、複数カラムを取得している結果です。以下に転載します。
(pluckメソッドの場合)
irb(main):002:0> User.pluck(:name, :age)
(0.0ms) SELECT “users”.”name”, “users”.”age” FROM “users”
=> [[“Ryosuke”, 18], [“Daigo”, 23], [“Hiroshi”, 35]]
[/bash]
(mapメソッドの場合)
irb(main):005:0> User.all.map{|user| [user.name, user.age]}
User Load (9.6ms) SELECT “users”.* FROM “users”
=> [[“Ryosuke”, 18], [“Daigo”, 23], [“Hiroshi”, 35]]
それぞれのSQL文の前にある()内の数値が、テーブルからデータを取得するのにかかった時間です。
pluckメソッドの場合は「(0.0ms)」のものが、mapメソッドの場合「(9.6ms)」もかかっているのです。pluckメソッドでは計測不能なほどの速度が、mapメソッドでは9.6msもかかっているのは、驚異的な速度差と言えます。
もちろん、PCの状態などによって変化しますし、今回のテスト環境はテーブルが小さすぎますので、ほとんどの場合はどちらでも0.0msになるのが普通です。
しかし、実際にこのような結果が現れることがあるほど、速度に違いがあるということでもあるのです。
そのため、パフォーマンスを重要視するRailsでは、pluckメソッドの使用を推奨しています。
メモリの使用量が違う
pluckメソッドは、mapメソッドに比べて、データ取得時に必要とするメモリが少なくなっています。
これは、pluckメソッドとmapメソッドの動作内容の違いによるものです。
具体的には、それぞれ以下のようにして必要なカラムを取得しています。
mapメソッド | テーブルから1レコードをメモリに読み込んで、その中の必要なカラムデータを取得、配列に格納していく |
---|---|
pluckメソッド | テーブルから必要なカラムを取得して配列に格納する |
mapメソッドがテーブルの1レコード(行)をすべてメモリ上に読み込むのに対して、pluckメソッドはテーブルから必要なデータを直接取得しますので、必要なメモリに大きな違いが出るのは当然と言えるでしょう。
必要なメモリが足らなければ、最悪の場合コンピュータ(サーバ)がフリーズしたり再起動したりなどの事態を引き起こしますので、安易に考えてはいけません。
そのため、pluckメソッドの使用を推奨されているわけです。
https://web-camp.io/magazine/archives/12654
pluckメソッドの使いどころと苦手なところ
ここまでに説明したように、pluckメソッドを利用することで、必要なカラムの配列を素早く、少ないメモリで取得できます。
また、プログラムコードとしても、pluckの方がシンプルで読みやすいものです。
そのため、pluckメソッドを使った方が良いシーンは数多くあるでしょう。
しかし、pluckメソッドも、どこでも使える万能なメソッドというわけではありません。
pluckメソッドにも苦手なシーンがあるのです。
pluckメソッドのデメリット
メリットだらけに見えるpluckメソッドにも、じつはデメリットと呼べるものがありますので、説明しましょう。
取得できるデータはただの配列である
ここまでに説明したように、pluckメソッドで取得できるのは、ただの配列です。
そのため、取得したカラムのデータに対して利用できるメソッドは、Arrayクラスのメソッドになります。
Arrayクラスにも豊富なメソッドがそろっていますが、ActiveRecordクラスほど高級な(複雑な処理をしてくれる)メソッドではありません。なにより、配列からはテーブルを更新できません。
そのため、取得したデータを使って処理を行いたい場合、特にテーブルの更新が発生するような場合は、pluckメソッドではなく、selectメソッドを使うべきでしょう。
N+1問題のような問題を引き起こす
前述の通り、pluckメソッドはテーブルから必要なデータを直接取得します。
つまり、pluckメソッドを使うと、そのたびにSQLが生成され、テーブルへのアクセスが発生するのです。
例えば、以下のプログラムコードのように、特定の金額以上のitemを持っているuserの名前を取得するような場合には、注意が必要です。
User.where(id: Item.where(“price >= ?”, 1000).pluck(:user_id)).pluck(:name)
実行すると、SELECTがpluckメソッドの数と同じく2回発生しています。
[bash]irb(main):014:0> User.where(id: Item.where(“price >= ?”, 1000).pluck(:user_id)).pluck(:name)
(4.0ms) SELECT “items”.”user_id” FROM “items” WHERE (price >= 1000)
(0.0ms) SELECT “users”.”name” FROM “users” WHERE “users”.”id” IN (?, ?) [[“id”, 3], [“id”, 2]]
=> [“Daigo”, “Hiroshi”]
[/bash]
以下のように、joinsメソッドやmergeメソッドを使うことで、1回のSELECTで済みます。
[bash]irb(main):016:0> User.joins(:items).merge(Item.where(“price >= ?”, 1000)).pluck(:name)
(0.0ms) SELECT “users”.”name” FROM “users” INNER JOIN “items” ON “items”.”user_id” = “users”.”id” WHERE (price >= 1000)
=> [“Hiroshi”, “Daigo”]
[/bash]
SELECTが発生するということは、テーブルへのアクセスが発生していますので、それだけパフォーマンスへ影響することになります。つまり、N+1問題と同等の問題です。(N+1問題については、「【Rails入門説明書】includesについて解説」を参照してください)
そのため、安易にpluckメソッドを使うのは避けた方が良いでしょう。
pluckメソッドの使いどころは?
ここまで説明したように、pluckメソッドにもデメリットがあります。そのため、安易に使うと思わぬ不具合や手戻りを発生させることになるでしょう。
しかし、これらのデメリットを許容できる(デメリットにならない)箇所では、積極的に使っていく方が良いと言えるでしょう
つまり、以下に「該当しない」場合が、使いどころと言えます。
・データ取得後、そのデータを更新してテーブルへ登録する
・何度も繰り返す処理である
https://web-camp.io/magazine/archives/12481
まとめ
pluckメソッドについて、お伝えしました。
pluckメソッドは、テーブルからデータを取得する速度が速く、メモリ消費も少なくて済みます。
そのため、テーブルから特定のカラムを取得する場合などは、mapメソッドやselectメソッドの代わりに使うことで、Railsアプリケーションのパフォーマンスを向上してくれることでしょう。
しかし、今回紹介したように、pluckメソッドにも弱点はありますので、使いどころを考えて使用しなければいけません。
プログラムは、同じことを実現する方法がいくつも存在することが多々あります。しかし、ほとんどの場合、そのアプリケーションの仕様や環境などから最適な手法は限られてくるものです。
ただし、最適な手法といったノウハウは、参考書や解説サイトで網羅できるものではありません。そのため、講師にマンツーマンで教えてもらえるスクールなどで元技術者の講師と直接話をして確認するのが、もっとも理想的と言えるかもしれません。
・pluckメソッドは、指定したカラムを配列として返してくれる
・pluckメソッドは、複数のカラムを指定できる
・mapメソッドでもpluckメソッドと同じ結果は得られるが、速度とメモリ消費量で劣る
・pluckメソッドは配列を返すため、その後テーブルへ登録する操作は難しい
・pluckメソッドは、つどSELECTが発生する(テーブルにアクセスする)ため、N+1問題のような問題が発生する場合がある
【インタビュー】1ヶ月でRubyをゼロから学び、Webエンジニアとして転職!
ブラジルから帰国し技術をつけようとRubyエンジニアを目指してWebCampでRubyを学び、見事Webエンジニアとして転職を果たした田中さんにお話を伺いました。
「Rubyの学習がしたい。基礎をしっかりと理解したい」
「転職のサポートがほしい」
と考えている方はぜひお読み下さい。
https://web-camp.io/magazine/archives/8535