【Rails】便利なpluckメソッドをマスターしよう!

Rails

pluckメソッドとは、引数に指定したカラムの値を配列で返してくれるメソッドです。

RubyではなくRailsで使用する事が出来るメソッドで、直接データベースからデータを取得します。

pluckメソッドの基本構文
1
2
3
モデル名.pluck(:カラム名)

モデル名.all.map(&:カラム名) # 上記と同じ

上記の様にmapメソッドに置き換える事が可能ですが、返り値を配列として取得する場合は、pluckメソッドを使った方がシンプルに記述することが出来ます。(後ほど詳しく解説します。)

pluckメソッドの基本的な使い方
リンクをコピーしました

pluckメソッドは、全てのデータではなく特定のカラムの値だけ取得したい場合に使うと便利です。

例えば、下記の様にownersテーブルのnameカラムの値だけ取得するには、pluckメソッドでOwnerモデルを指定し、引数にカラム名nameを渡します。

ownersテーブルの構成

コンソール | nameカラムの値だけを取得する
1
2
3
4
Owner.pluck(:name)
SELECT `owners`.`name` FROM `owners`

=> ["田中", "伊藤", "高橋", "加藤"] # 返り値

返り値は、pluckメソッドの引数に渡したnameカラムの値が配列に格納されています。この様にpluckメソッドを使うと、配列で返ってくる事が確認出来ます。

それでは、他の使い方について解説していきます。

引数に複数指定
リンクをコピーしました

pluckメソッドは、取得するカラムを複数指定する事が出来ます。複数カラムを指定した時の返り値は、二次元配列で返ります。

下記の様に、先ほど指定したnameカラムの他にidカラムを引数に渡して実行します。

コンソール | idカラムとnameカラムの値を取得する
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カラムの値がそれぞれ配列に格納されます。

コンソール | pluckメソッドに引数を渡さない場合
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の値が重複して配列に格納されます。

コンソール | ageカラムの値が重複して取得される
1
2
3
4
Owner.pluck(:age)
SELECT `owners`.`age` FROM `owners`

=> [23, 44, 65, 23] # 返り値

下記の様にdistinctメソッドを使うと、重複する値を取り除いてユニークの値を取得する事が出来ます。

コンソール | ageカラムの値が重複を取り除いて取得する
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カラムの値を配列で取得しています。

whereメソッドでageカラムの値を絞り込む例

コンソール | 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テーブルを例に確認します。

ownersテーブルとcatsテーブルの例

このテーブルでオーナーが飼っている猫の種類を配列で取得したいときに、下記の様にjoinsメソッドでテーブルを内部結合し、pluckメソッドで結合先のcatsテーブルのspeciesカラムの値を取得することが出来ます。

ownersテーブルとcatsテーブルを内部結合する例

コンソール | 結合先の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メソッドで重複を取り除く事が出来ます。

コンソール | 結合先のcatsテーブルのspeciesカラムの値をユニークで取得する
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メソッドで取得します。

コンソール | ownersテーブルのidカラムのデータを取得する
1
2
3
Owner.ids
SELECT `owners`.`id` FROM `owners`
=> [1, 2, 3, 4] # 返り値

返り値を確認すると、主キーのカラムデータを配列で取得されています。

pluckメソッドの返り値
リンクをコピーしました

pluckメソッドの返り値は、配列です。
その為、pluckメソッドの後にメソッドチェーンを使って他のクエリメソッドを繋げることが出来ないので注意してください。

メソッドチェーンは、ActiveRecord::Relationオブジェクトに対して使うことが出来るので、配列が返り値のpluckメソッドには使うことが出来ません。

メソッドチェーンを使った場合
リンクをコピーしました

pluckメソッドの後にメソッドチェーンを使った場合は、下記のようにNoMethodErrorが発生します。

コンソール | pluckメソッドの後にメソッドチェーンを使った場合
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メソッドがあります。下記はどちらも特定のカラムのデータを取得するという点は同じですが、異なる点がいくつかあります。

pluckメソッドとmapメソッド
1
2
3
モデル名.pluck(:カラム名)

モデル名.all.map(&:カラム名) # 上記と同じ

この章では、2つのメソッドの違いを比較しながら解説していきます。

mapメソッドについての詳しい解説は、mapメソッドの基礎から応用をマスターして、効率的なコードを書けるようにしよう!を参考にして下さい。

発行されるSQL文の違い
リンクをコピーしました

pluckメソッドとmapメソッドは、メソッドを実行する際に発行されるSQL文が異なります。
例えば、ownersテーブルのageカラムのデータを各メソッドで取得すると下記の様になります。

コンソール | pluckメソッドとmapメソッドで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が発行されます。

SQL | pluckメソッドを実行した場合
1
SELECT `owners`.`age` FROM `owners`

SELECT文には、SELECT owners.ageの様にownersテーブルのageカラムが指定されているので、このSQL文では「ownersテーブルのageカラムのデータ」を取得している事が分かります。

pluckメソッドでageカラムのデータが取得される例

一方で、mapメソッドを実行した場合は下記の様なSQLが発行されます。

SQL | mapメソッドを実行した場合
1
SELECT `owners`.* FROM `owners`

SELECT文には、SELECT owners.*の様にownersテーブルの全てのカラムが指定されているので、このSQL文では「ownersテーブルの全てのカラムのデータ」を取得している事が分かります。

mapメソッドでageカラムのデータが取得される例

この事から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文は異なります。

PikawakaマークpluckメソッドとmapメソッドのSQLの違い

  1. pluckメソッドは、SQLの段階から取得するカラムのデータを絞り込んでいる
  2. mapメソッドは、全てのデータを取得した後そのデータから特定のカラムのデータを取得している

mapではなくpluckを使う場面
リンクをコピーしました

特定のカラムのデータしか使わない場面では、pluckメソッドを使いましょう。
理由としては、必要なのは特定のデータだけなのに、mapメソッドを使うと全てのデータを読み込むのでメモリの無駄使いになり、パフォーマンスが低下するからです。

コンソール | pluckメソッドとmapメソッドで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]

先ほども解説した上記のコードでは、ageカラムのデータしか必要ないのでmapメソッドではなくpluckメソッドを使うことによってageカラムのデータしか読み込まれないのでメモリ使用量も削減されます。

pluckではなくmapを使う場面
リンクをコピーしました

インスタンス化したオブジェクトからデータを取得する場合は、mapメソッドを使いましょう。
理由としては、pluckメソッドはインスタンス化したオプジェクトに対しても毎回SQLを実行してしまいパフォーマンスの低下に繋がるからです。

それでは、下記の様にOwnerモデルの全てのレコードを取得して、2つのメソッドを2000回繰り返して処理速度を確認します。

コンソール |2つのメソッドのベンチーマーク
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回発行されています。

pluckメソッドのベンチマーク

一方で、mapメソッドはメモリ内で計算するだけなので、下記の様にループ処理の中でも1回だけのSQLの発行になります。SQLはownersテーブルから全てのデータを取得しています。

SQL | mapメソッドで発行されるSQL
1
SELECT `owners`.* FROM `owners`

下記のプログラム処理に掛かった合計時間であるtotalの数値を確認すると、pluckメソッドが1.750148に対してmapメソッドは0.020640です。mapメソッドの方がpluckメソッドに対して、87.5倍速いことが確認出来ました。

コンソール |2つのメソッドのベンチーマーク結果
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メソッドの後にメソッドチェーンを使う事は出来ない