#内容
rails学習中にpluckとibsを使用する機会があったのでまとめる。
#pluckとは?
pluckは、1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信するのに使用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。
*公式ドキュメントより
my_room_ids = current_user.entries.pluck(:room_id)
pryで確認するとこうなる
pry(#<RoomsController>)> my_room_ids
=> [7, 8, 9, 10]
上記はcurrent_userに紐付いているentriesのmodelから:room_idの値の配列をmy_room_idsに代入するコードとなる。第2引数を設定すればそれも出力できる。pluckはmapに比べて標準的なデータベースの探索のみなら、約4倍程の速度を出せると言われています。
*注意点
pluckメソッドはクエリを直接トリガするので、その後ろに他のスコープをチェインすることはできない。
#引数なしの場合
pluckメソッドに引数を渡さない場合は、全カラムのデータを配列に入れて返します。
返り値は、二次元配列で返ります。
pluckメソッドに引数なしでOwnerモデルを指定すると、下記の様にid, name, ageカラムの値がそれぞれ配列に格納されます。
pluckメソッドに引数を渡さない場合
Owner.pluck
SELECT `owners`.* FROM `owners`
=> [
[1, "田中", 23], # id, name, ageカラムの値が格納される
[2, "伊藤", 44],
[3, "高橋", 65],
[4, "加藤", 23]
]
全カラムのデータは、さらに配列に格納されるので二次元配列で返ります。
#重複を取り除く
pluckメソッドは、重複を取り除くdistinctメソッドと併用する事が出来ます。下記の様にpluckメソッドの引数にageカラムを指定すると、 23の値が重複して配列に格納されます。
ageカラムの値が重複して取得される
Owner.pluck(:age)
SELECT `owners`.`age` FROM `owners`
=> [23, 44, 65, 23] # 返り値
下記の様にdistinctメソッドを使うと、重複する値を取り除いてユニークの値を取得する事が出来ます。
コンソール | ageカラムの値が重複を取り除いて取得する
Owner.distinct.pluck(:age)
SELECT DISTINCT `owners`.`age` FROM `owners`
=> [23, 44, 65] # 返り値
返り値を確認すると、それぞれユニークな値が配列に格納されている事が確認出来ます。
条件を指定する
pluckメソッドは、条件を抽出してくれるwhereメソッドと併用する事が出来ます。
whereメソッドでageカラムの値を絞り込む例
ageカラムの値が60以下のidカラムの値を取得する
Owner.where('age <= 60').pluck(:id)
SELECT `owners`.`id` FROM `owners` WHERE (age <= 60)
=> [1, 2, 4] # 返り値
抽出したデータから.pluck(:id)でidカラムの値を配列で取得します。
#関連するテーブルのカラムの値を取得する
pluckメソッドは、関連するテーブル同士を内部結合してくれるjoinsメソッドを併用する事で関連するテーブルのカラムの値も取得する事が出来ます。
飼い主を管理するownersテーブルと猫を管理するcatsテーブルを例に確認します。
ownersテーブルとcatsテーブルの例
このテーブルでオーナーが飼っている猫の種類を配列で取得したいときに、下記の様にjoinsメソッドでテーブルを内部結合し、pluckメソッドで結合先のcatsテーブルのspeciesカラムの値を取得することが出来ます。
ownersテーブルとcatsテーブルを内部結合する例
結合先のcatsテーブルのspeciesカラムの値を取得する
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カラムの値をユニークで取得する
Owner.joins(:cats).distinct.pluck(:species)
SELECT DISTINCT `species` FROM `owners` INNER JOIN `cats` ON `cats`.`owner_id` = `owners`.`id`
=> ["ミックス", "スコティッシュ・フォールド", "アメリカン・ショートヘア"] # 返り値
それぞれユニークな値を配列に格納して返している事が返り値で確認する事が出来ます。
メソッドチェーンは、ActiveRecord::Relationオブジェクトに対して使うことが出来るので、配列が返り値のpluckメソッドには使うことが出来ません。
メソッドチェーンを使った場合
pluckメソッドの後にメソッドチェーンを使った場合は、下記のようにNoMethodErrorが発生します。
pluckメソッドの後にメソッドチェーンを使った場合
Owner.pluck(:id).where('age <= 60')
NoMethodError (undefined method `where' for [1, 2, 3, 4]:Array)
上記は、pluckメソッドの後にメソッドチェーン(.)を使ってwhereメソッドを呼び出そうとしましたが、pluckメソッドの返り値が配列(Array)なので、配列(Array)クラスにwhereメソッドはないというエラーが発生しています。
クエリメソッドを併用する場合
pluckメソッドの後にはメソッドチェーンで他のクエリメソッドを呼び出せないので、クエリメソッドを併用して使う場合は、下記の様にpluckメソッドを最後にしましょう。
Owner.where('age <= 60').pluck(:id)
SELECT `owners`.`id` FROM `owners` WHERE (age <= 60)
=> [1, 2, 4]
他のクエリメソッドと併用する場合は、pluckメソッドの使用する順番にだけ気をつけてください。また、特に配列で返る必要がなければ特定のカラムのデータを取得するselectメソッドがあります。このselectメソッドの返り値はActiveRecord::Relationオブジェクトなので、メソッドチェーンを使うことが出来ます。
補足説明
クエリメソッドとは、whereメソッドやorderメソッドなどデータベースを検索をする際に様々な条件を作ってくれるメソッドのことです。条件の結果をActiveRecord::Relationオブジェクトとして返してくれます。
#pluckメソッドとmapメソッドの違い
どちらも特定のカラムのデータを取得するという点は同じですが、異なる点がいくつかあります。
pluckメソッドとmapメソッド
モデル名.pluck(:カラム名)
モデル名.all.map(&:カラム名) # 上記と同じ
発行されるSQL文の違い
pluckメソッドとmapメソッドは、メソッドを実行する際に発行されるSQL文が異なります。
例えば、ownersテーブルのageカラムのデータを各メソッドで取得すると下記の様になります。
pluckメソッドとmapメソッドでageカラムのデータを取得する
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メソッドを実行した場合
SELECT `owners`.`age` FROM `owners`
SELECT文には、SELECT owners.ageの様にownersテーブルのageカラムが指定されているので、このSQL文では「ownersテーブルのageカラムのデータ」を取得している事が分かります。
一方で、mapメソッドを実行した場合は下記の様なSQLが発行されます。
SQL | mapメソッドを実行した場合
SELECT `owners`.* FROM `owners`
SELECT文には、SELECT owners.*の様にownersテーブルの全てのカラムが指定されているので、このSQL文では「ownersテーブルの全てのカラムのデータ」を取得している事が分かります。
この事からOwner.all.map(&:age)では、一旦ownersテーブルから全てのデータを取得してからageカラムのデータだけ配列に入れ直している事が分かります。
この様にpluckメソッドとmapメソッドの返り値は同じですが、発行されるSQL文は異なります。
pluckメソッドとmapメソッドのSQLの違い
pluckメソッドは、SQLの段階から取得するカラムのデータを絞り込んでいる
mapメソッドは、全てのデータを取得した後そのデータから特定のカラムのデータを取得している
mapではなくpluckを使う場面
特定のカラムのデータしか使わない場面では、pluckメソッドを使いましょう。
理由としては、必要なのは特定のデータだけなのに、mapメソッドを使うと全てのデータを読み込むのでメモリの無駄使いになり、パフォーマンスが低下するからです。
pluckではなくmapを使う場面
インスタンス化したオブジェクトからデータを取得する場合は、mapメソッドを使いましょう。
理由としては、pluckメソッドはインスタンス化したオプジェクトに対しても毎回SQLを実行してしまいパフォーマンスの低下に繋がるからです。
#pluckのまとめ
引数に指定したカラムの値を配列で返してくれるメソッド
引数に複数指定すると、二次元配列で返る
pluckメソッドの後にメソッドチェーンを使う事は出来ない
#idsとは?
主キーのカラムデータを取得する
pluckメソッドは、特定のカラムデータを配列で取得することが出来ますが、主キーのカラムデータを配列で取得する場合は、idsメソッドを使うと便利です。
下記のownersテーブルの主キーのデータをidsメソッドで取得します。
ownersテーブルのidカラムのデータを取得する
Owner.ids
SELECT `owners`.`id` FROM `owners`
=> [1, 2, 3, 4] # 返り値
返り値を確認すると、主キーのカラムデータを配列で取得されています。