Rails

【Rails】DBへの負荷を抑えながら、カラム情報を複数取得できるpluckメソッドが便利なのでおすすめしたい

Rails

 

今回は効率良くカラム情報を複数取得することができるメソッド、pluckメソッドについて解説したいと思います。selectメソッドとmapメソッドを併用して複数のカラム情報を取得することもできますが、pluckメソッドを使用することでより簡単に取得ができるため、ぜひ使って欲しいメソッドの1つです。

厳密にpluckメソッドには、ActiveRecordとEnumerableモジュールの2種類があります。ここについても以下で説明します。

pluckメソッドとは

ActiveRecordのpluckメソッド

ActiveRecordのpluckメソッドは、カラム情報を1個もしくは複数取得することができるメソッドです。pluckメソッドについて、Railsガイドには以下のように記されています。

pluckは、1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信するのに使用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。

参照: Railsガイド pluck

 

取得したいカラムをpluckメソッドのキーに指定することで、対応する値全てを配列で取得することができます。以下を例に載せておきます。

# Postモデルからtitleカラムに格納されている値を取得する
> Post.pluck(:title)
=> ["テスト投稿1", "テスト投稿2", "テスト投稿3"]

# Postモデル方titleカラムの値とuser_idを一度に取得する
> Post.pluck(:id, :user_id)
=> [["テスト投稿1", 1], ["テスト投稿2", 2], ["テスト投稿2", 3]]

selectメソッドとmapメソッドを併用すると以下のように記述できます。

> Post.select(:title, :user_id).map { |c| [c.title, c.user_id] }
=> [["テスト投稿1", 1], ["テスト投稿2", 1], ["テスト投稿3", 1]]

 

pluckメソッドの前にwhereで条件検索をかけることもできます。

> User.where(active: true).pluck(:name)
=> ["田中", "佐藤", "高橋", "加藤", "高橋"]

 

重複を取り除きたい場合は、pluckメソッドの前にdistinctを指定します。

> User.distinct.pluck(:name)
=> ["田中", "佐藤", "高橋", "加藤"]

Enumerableのpluckメソッド

Enumerableモジュール

 

Enumerableモジュールとは、繰り返し処理をより簡潔に書くために提供されているメソッド(モジュール, 拡張機能)です。collectメソッドやfilterメソッドがEnumerableモジュールにあたります。

繰り返しを行なうクラスのための Mix-in。このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。

Array, Hash, Range, Enumerator等のクラスで、 Enumerableモジュールはインクルードされています。ただし、効率化のため、そのクラスでEnumerableと同名・同等の機能を再定義(オーバーライド)しているケースも少なくなく、特にArrayクラスでは同名のメソッドを再定義していることが多いです。

参照: module Enumerable

 

Enumerableのpluckメソッドは、配列情報を1個もしくは複数取得できるメソッドです。以下のようにキーを指定することで、Enumerableでもpluckメソッドを使用することができます。

# nameを取得
[
  { id: 1, name: "佐藤" },
  { id: 2, name: "田中" },
  { id: 3, name: "高橋" }
  { id: 4, name: "加藤" }
].pluck(:name)
=> [ "佐藤", "田中", "高橋", "加藤" ]


# idとnameを一度に取得
[
  { id: 1, name: "佐藤" },
  { id: 2, name: "田中" },
  { id: 3, name: "高橋" }
  { id: 4, name: "加藤" }
].pluck(:name)
=> [ [1, "佐藤"], [2, "田中"], [3, "高橋"], [4, "加藤"] ]

 

これは外部APIから取得したJSONデータを扱うときに重宝します。

require "httparty"
require "active_support"
require "active_support/core_ext"

response = HTTParty.get('http://api.stackexchange.com/2.2/questions?site=stackoverflow')
questions = JSON.parse(response.body)["items"]
questions.pluck("title")

# => [
#   "JavaScript to Python - Interpreting JavasScript .filter() to a Python user",
#   "Nuxt generate and firebase gives timer warning",
#   "Variable expected error when I increment the value of a map",
#   ...
# ]

効率が良い理由

pluckメソッドの最大の特徴としては、必要なカラム情報を取得するためだけに大量のレコードを読み込まないということです。つまり、効率良くDBへの負荷を抑えながらカラム情報を取得することができます。

pluckを使用せずselectやmapでカラム情報を取得した場合、該当テーブルの全てのカラム情報を取得してしまいます。以下を見ると全件検索のクエリ(*)が走っていますね。

User.all.map(&:name)
# SELECT "users.*" from "users"
=> [ "佐藤", "田中", "高橋", "加藤" ]

 

一方、pluckメソッドを使用することで欲しいカラム情報だけを取り出すクエリをかけることができます。SELECT “users.name”のクエリがそれに該当します。

% User.pluck(:name)
# SELECT "users.name" from "users"
=> [ "佐藤", "田中", "高橋", "加藤" ]

 

このことから、pluckメソッドを用いることで記述を簡単にすることだけでなく、DBへの負荷を抑えたカラム情報取得が可能になります。

注意点

pluckメソッドはとても便利なメソッドですが、2点注意しなければならないことがあります。それはオーバーライドを行うモデルメソッドは使用できないことpluckメソッドの後ろにチェーンすることはできないということです。

オーバーライドを行うモデルメソッドには使用できない

オーバーライドを行うモデルメソッドには使用できない例を以下に載せておきます。以下の例はRailsガイドから引っ張ってきたコードになります。

class Client < ApplicationRecord
  def name
    "私は#{super}"
  end
end

Client.select(:name).map &:name
# => ["私はDavid", "私はJeremy", "私はJose"]

Client.pluck(:name)
# => ["David", "Jeremy", "Jose"]

参照: Railsガイド pluck

pluckメソッドの後ろにチェーンすることはできない

メソッドチェーンとはpluck(:title).limit(5)のようにメソッド同士を連結させることを言います。pluckメソッドではこのような記述をすることができません。

例えば以下のように記述してしまうと、配列(Array)に対してlimitメソッドがundefinedになってしまいます。

Post.pluck(:title).limit(3)
=> NoMethodError: undefined method `limit' for ["テスト投稿1", "テスト投稿2", "テスト投稿3"]:Array

 

この場合、pluckメソッドの前でメソッドチェーンを行うことで解決することができます。

Post.limit(3).pluck(:title)
=> ["テスト投稿1", "テスト投稿2", "テスト投稿3"]

idを配列で取得したい場合

主キーを配列で取得したい場合に限り、idsというメソッドを使用することができます。idsは、テーブルの主キーを使用するリレーションのIDをすべて取り出すのに使用できます。

Person.ids
# SELECT id FROM people
=> [1, 3, 8]
class Person < ApplicationRecord
  self.primary_key = "person_id"
end

Person.ids
# SELECT person_id FROM people
=> [1, 3, 8]

pluckメソッドやmapメソッドを使用することでも同様の結果を取得できます。

Person.pluck(:id)
=> [1, 3, 8]

Person.all.map(&:id)
=> [1, 3, 8]

まとめ

  • pluckメソッドには、ActiveRecordとEnumerableモジュールの2種類がある
  • ActiveRecordのpluckメソッドは、カラム情報を1個もしくは複数取得することができるメソッド
  • Enumerableのpluckメソッドは、配列情報を1個もしくは複数取得できるメソッド
  • pluckメソッドは必要なカラム情報を取得するためだけに大量のレコードを読み込まないという特徴がある
  • pluckメソッドはオーバーライドを行うモデルメソッドは使用できない
  • pluckメソッドの後ろにチェーンすることはできない

参考

 

 

今回はカラム情報を複数取得することができるメソッド、pluckメソッドについて解説しました。pluckメソッドは、大規模なクエリや使用頻度の高いクエリで使用するとパフォーマンスが向上するので積極的に使いたいメソッドの1つですね。