更新日:
【Rails】 便利なpluckメソッドの使い方をマスターしよう!
pluckメソッドとは、引数に指定したカラムの値を配列で返してくれるメソッドです。
1
2
3
モデル名.pluck(:カラム名)
モデル名.all.map(&:カラム名) # 上記と同じ
上記の様にmapメソッドに置き換える事が可能ですが、返り値を配列として取得する場合は、pluckメソッドを使った方がシンプルに記述することが出来ます。(後ほど詳しく解説します。)
Railsで使用する事が出来るメソッドで、データベースから直接データを取得します。
pluckメソッドの基本的な使い方
pluckメソッドは、全てのデータではなく特定のカラムの値だけ取得したい場合に使うと便利です。
例えば、下記の様にownersテーブルのnameカラムの値だけ取得するには、pluckメソッドでOwner
モデルを指定し、引数にカラム名name
を渡します。
1
2
3
4
Owner.pluck(:name)
SELECT `owners`.`name` FROM `owners`
=> ["田中", "伊藤", "高橋", "加藤"] # 返り値
返り値は、pluckメソッドの引数に渡したnameカラムの値が配列に格納されています。この様にpluckメソッドを使うと、配列で返ってくる事が確認出来ます。
それでは、他の使い方について解説していきます。
引数に複数指定
pluckメソッドは、取得するカラムを複数指定する事が出来ます。複数カラムを指定した時の返り値は、二次元配列で返ります。
下記の様に、先ほど指定したnameカラムの他にidカラムを引数に渡して実行します。
1
2
3
4
Owner.pluck(:id, :name)
SELECT `owners`.`id`, `owners`.`name` FROM `owners`
=> [[1, "田中"], [2, "伊藤"], [3, "高橋"], [4, "加藤"]] # 返り値
pluckメソッドの引数に渡したidカラムとnameカラムの値が、二次元配列に格納されて返ります。
二次元配列とは、配列の中に要素として配列が格納された形のことで、上記はidカラムとnameカラムの値がそれぞれ配列に格納され、その配列が要素として更に配列に格納されます。詳しくは、二次元配列とは?を参考にしてください。
引数なしの場合
pluckメソッドに引数を渡さない場合は、全カラムのデータを配列に入れて返します。
返り値は、二次元配列で返ります。
pluckメソッドに引数なしでOwner
モデルを指定すると、下記の様にid, name, age
カラムの値がそれぞれ配列に格納されます。
1
2
3
4
5
6
7
8
9
Owner.pluck
SELECT `owners`.* FROM `owners`
=> [
[1, "田中", 23], # id, name, ageカラムの値が格納される
[2, "伊藤", 44],
[3, "高橋", 65],
[4, "加藤", 23]
]
全カラムのデータは、さらに配列に格納されるので二次元配列で返ります。
重複を取り除く
pluckメソッドは、重複を取り除くdistinctメソッドと併用する事が出来ます。下記の様にpluckメソッドの引数にageカラムを指定すると、 23の値が重複して配列に格納されます。
1
2
3
4
Owner.pluck(:age)
SELECT `owners`.`age` FROM `owners`
=> [23, 44, 65, 23] # 返り値
下記のようにdistinctメソッドを使うと、重複する値を取り除いてユニークの値を取得する事が出来ます。
1
2
3
4
Owner.distinct.pluck(:age)
SELECT DISTINCT `owners`.`age` FROM `owners`
=> [23, 44, 65] # 返り値
返り値を確認すると、それぞれユニークな値が配列に格納されている事が確認出来ます。distinctメソッドについて、詳しくは【Rails】distinctメソッドでユニークなデータを取得する方法を参考にしてください。
条件を指定する
pluckメソッドは、条件を抽出してくれるwhereメソッドと併用する事が出来ます。
下記はwhereメソッドとpluckメソッドを併用して、ageカラムの値が60以下のidカラムの値を配列で取得しています。
1
2
3
4
Owner.where('age <= 60').pluck(:id)
SELECT `owners`.`id` FROM `owners` WHERE (age <= 60)
=> [1, 2, 4] # 返り値
Owner.where('age <= 60')
で緑の部分が抽出されます。そして、抽出したデータから.pluck(:id)
でidカラムの値を配列で取得します。whereメソッドについて、詳しくは【Rails】whereメソッドを徹底解説!を参考にしてください。
関連するテーブルのカラムの値を取得する
pluckメソッドは、関連するテーブル同士を内部結合してくれるjoinsメソッドを併用する事で関連するテーブルのカラムの値も取得する事が出来ます。
飼い主を管理するownersテーブルと猫を管理するcatsテーブルを例に確認します。
このテーブルでオーナーが飼っている猫の種類を配列で取得したいときに、下記の様にjoinsメソッドでテーブルを内部結合し、pluckメソッドで結合先のcatsテーブルのspeciesカラムの値を取得することが出来ます。
1
2
3
4
Owner.joins(:cats).pluck(:species)
SELECT `species` FROM `owners` INNER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id`
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア", "ミックス"] #返り値
Owner.joins(:cats)
で、ピンクの部分の値(owners.id=cats.owner_id
の結合条件)が一致するデータだけを結合します。そして、結合したデータから.pluck(:species)
で結合先のcatsテーブルのspeciesカラムの値を配列で取得します。
取得した値はミックス
が重複してるので、下記の様にdistinctメソッドで重複を取り除く事が出来ます。
1
2
3
4
Owner.joins(:cats).distinct.pluck(:species)
SELECT DISTINCT `species` FROM `owners` INNER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id`
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア"] # 返り値
それぞれユニークな値を配列に格納して返している事が返り値で確認する事が出来ます。joinsメソッドの内部結合については、【Rails】joinsメソッドのテーブル結合からネストまでの解説書を参考にしてください。
主キーのカラムデータを取得する
pluckメソッドは、特定のカラムデータを配列で取得することが出来ますが、主キーのカラムデータを配列で取得する場合は、idsメソッドを使うと便利です。
下記のownersテーブルの主キーのデータをidsメソッドで取得します。
1
2
3
Owner.ids
SELECT `owners`.`id` FROM `owners`
=> [1, 2, 3, 4] # 返り値
返り値を確認すると、主キーのカラムデータを配列で取得されています。
pluckメソッドの返り値
pluckメソッドの返り値は、配列です。
その為、pluckメソッドの後にメソッドチェーンを使って他のクエリメソッドを繋げることが出来ないので注意してください。
メソッドチェーンは、ActiveRecord::Relationオブジェクト
に対して使うことが出来るので、配列が返り値のpluckメソッドには使うことが出来ません。
メソッドチェーンを使った場合
pluckメソッドの後にメソッドチェーンを使った場合は、下記のようにNoMethodError
が発生します。
1
2
Owner.pluck(:id).where('age <= 60')
NoMethodError (undefined method `where' for [1, 2, 3, 4]:Array)
上記は、pluckメソッドの後にメソッドチェーン(.
)を使ってwhereメソッドを呼び出そうとしましたが、pluckメソッドの返り値が配列(Array)なので、配列(Array)クラスにwhereメソッドはないというエラーが発生しています。
クエリメソッドを併用する場合
pluckメソッドの後にはメソッドチェーンで他のクエリメソッドを呼び出せないので、クエリメソッドを併用して使う場合は、下記の様にpluckメソッドを最後にしましょう。
1
2
3
4
Owner.where('age <= 60').pluck(:id)
SELECT `owners`.`id` FROM `owners` WHERE (age <= 60)
=> [1, 2, 4]
他のクエリメソッドと併用する場合は、pluckメソッドの使用する順番にだけ気をつけてください。また、特に配列で返る必要がなければ特定のカラムのデータを取得するselectメソッドがあります。このselectメソッドの返り値はActiveRecord::Relationオブジェクト
なので、メソッドチェーンを使うことが出来ます。
詳しくは、selectメソッドとpluckメソッドの異なる点とは?を参照してください。
pluckメソッドとmapメソッドの違い
冒頭でも紹介しましたが、特定のカラムのデータを取得する方法にはpluckメソッドの他にmapメソッドがあります。下記はどちらも特定のカラムのデータを取得するという点は同じですが、異なる点がいくつかあります。
1
2
3
モデル名.pluck(:カラム名)
モデル名.all.map(&:カラム名) # 上記と同じ
この章では、2つのメソッドの違いを比較しながら解説していきます。
mapメソッドについての詳しい解説は、mapメソッドの基礎から応用をマスターして、効率的なコードを書けるようにしよう!を参考にして下さい。
発行されるSQL文の違い
pluckメソッドとmapメソッドは、メソッドを実行する際に発行されるSQL文が異なります。
例えば、ownersテーブルのageカラムのデータを各メソッドで取得すると下記の様になります。
1
2
3
4
5
6
7
Owner.pluck(:age) # pluckメソッドで取得する場合
SELECT `owners`.`age` FROM `owners`
=> [23, 44, 65, 23]
Owner.all.map(&:age) # mapメソッドで取得する場合
SELECT `owners`.* FROM `owners`
=> [23, 44, 65, 23]
返り値を確認すると、両方のメソッドとも配列が返ってますが発行されているSQL文が異なっています。
pluckメソッドで実行した場合は、下記の様なSQLが発行されます。
1
SELECT `owners`.`age` FROM `owners`
SELECT文には、SELECT owners.age
の様にownersテーブルのageカラムが指定されているので、このSQL文では「ownersテーブルのageカラムのデータ」を取得している事が分かります。
一方で、mapメソッドを実行した場合は下記の様なSQLが発行されます。
1
SELECT `owners`.* FROM `owners`
SELECT文には、SELECT owners.*
の様にownersテーブルの全てのカラムが指定されているので、このSQL文では「ownersテーブルの全てのカラムのデータ」を取得している事が分かります。
この事からOwner.all.map(&:age)
では、一旦ownersテーブルから全てのデータを取得してからageカラムのデータだけ配列に入れ直している事が分かります。このコードは、&:
を使ってmapメソッドを省略した書き方をしているので、下記の様に省略しないで記述すると理解しやすいです。
1
2
3
4
Owner.all.map { |owner| owner.age } # 全てのデータを取得した後,ageカラムの値を配列に格納
SELECT `owners`.* FROM `owners`
=> [23, 44, 65, 23]
上記はOwner.all
でデータを全て取得して、そのデータをmapメソッドを使ってageカラムの値だけ取り出して配列に入れています。
この様にpluckメソッドとmapメソッドの返り値は同じですが、発行されるSQL文は異なります。
mapではなくpluckを使う場面
特定のカラムのデータしか使わない場面では、pluckメソッドを使いましょう。
理由としては、必要なのは特定のデータだけなのに、mapメソッドを使うと全てのデータを読み込むのでメモリの無駄使いになり、パフォーマンスが低下するからです。
1
2
3
4
5
6
7
Owner.pluck(:age) # pluckメソッドで取得する場合
SELECT `owners`.`age` FROM `owners`
=> [23, 44, 65, 23]
Owner.all.map(&:age) # mapメソッドで取得する場合
SELECT `owners`.* FROM `owners`
=> [23, 44, 65, 23]
先ほども解説した上記のコードでは、ageカラムのデータしか必要ないのでmapメソッドではなくpluckメソッドを使うことによってageカラムのデータしか読み込まれないのでメモリ使用量も削減されます。
pluckではなくmapを使う場面
インスタンス化したオブジェクトからデータを取得する場合は、mapメソッドを使いましょう。
理由としては、pluckメソッドはインスタンス化したオプジェクトに対しても毎回SQLを実行してしまいパフォーマンスの低下に繋がるからです。
それでは、下記の様にOwnerモデルの全てのレコードを取得して、2つのメソッドを2000回繰り返して処理速度を確認します。
1
2
3
4
5
6
owners = Owner.all
n = 2000
Benchmark.bm do |x|
x.report { n.times { owners.pluck(:id) } }
x.report { n.times { owners.map(&:id) } }
end
ループ処理の中でpluckメソッドを実行した場合は、下記の様に毎回SQLが発行されていることが分かります。この場合だと2000回発行されています。
一方で、mapメソッドはメモリ内で計算するだけなので、下記の様にループ処理の中でも1回だけのSQLの発行になります。SQLはownersテーブルから全てのデータを取得しています。
1
SELECT `owners`.* FROM `owners`
下記のプログラム処理に掛かった合計時間であるtotalの数値を確認すると、pluckメソッドが1.750148
に対してmapメソッドは0.020640
です。mapメソッドの方がpluckメソッドに対して、87.5倍速いことが確認出来ました。
1
2
3
user system total real
pluck 1.464628 0.285520 1.750148 ( 3.774797)
map 0.017986 0.002654 0.020640 ( 0.028815)
もし今書いているコードの中でmapメソッドで良いのにpluckメソッドを使ってる箇所があったり、pluckメソッドで良いのにmapメソッドを使ってる箇所がある人は今すぐコードを見直してリファクタリングしましょう。
修正すれば、あなたの作ってるアプリケーションのパフォーマンスが劇的に良くなります。
この記事のまとめ
- 引数に指定したカラムの値を配列で返してくれるメソッド
- 引数に複数指定すると、二次元配列で返る
- pluckメソッドの後にメソッドチェーンを使う事は出来ない