はじめに
railsのpluck
メソッドはActiveRecordを生成せず、指定したカラムの値が格納された配列を生成するので、パフォーマンスを改善したい時に重宝します。
ただ、配列で値が返ってくるが故に、その後の扱いが抜群に面倒です。
今回は、pluck
メソッドで省エネに値を取得しつつ、開発体験を損なわない方法を紹介します。
実際にこの方法でAPIの応答時間を1/5に抑えられたこともありますので、
是非参考にしてみてください!
少し詳しめに書いてますんで、やり方だけ知りたい方は最後のまとめだけ見てってください。
pluckの使い方・問題点
pluckメソッドの使い方
pluck
メソッドは以下のように使用することができます。
User.all.pluck(:id)
# => [1, 2]
また、この時に発行されるSQLクエリは以下のような感じで、必要なカラムだけ取得するので、DBにも優しい(らしい)です。
SELECT `users`.`id` FROM `users`
そのため、ActiveRecord
を用いてデータを取得するよりも、パフォーマンスに優れています。
実際にベンチマークを測定してみると違いがよくわかります。
( ちなみにusersテーブルは約1800行 30列のデータを入れています。)
Benchmark.bm 10 do |r|
r.report "pluck" do
User.all.pluck(:id)
end
r.report "not pluck" do
User.all.to_a
end
end
user system total real
pluck 0.000362 0.001236 0.001598 ( 0.002610)
not pluck 0.022077 0.001174 0.023251 ( 0.028349)
pluckメソッドの問題点
手軽にパフォーマンスを上げれるpluck
メソッドですが、値の使い勝手が悪いという問題点があります。
先述の例のように1つのカラムの値が欲しいだけなら無問題ですが、複数のカラムを取得した場合
User.pluck(:id, :name)
# => [[1, "太郎"], [2, "花子"]]
という風な配列が返ってくるため、大変使いづらいです。
1人目のname
だけ取り出したい時はusers[0][0]
と記述しなければならず、書く人にも読む人にも優しくないコードになります。
本題
解決策
ハッシュを生成しましょう。
意味が伝わらないのは、数字を使ってるからなので、文字列で値を指定できるハッシュを使いましょう。
users = User.pluck(:id, :name).map do |values|
{
id: values[0],
name: values[1]
}
end
puts users
# => [
{
id: 1,
name: "太郎"
},
{
id: 2,
name: "花子"
}
]
puts users.first[:name]
# => 太郎
これで値の取り回しが良くなりました。
ただし、問題点があります。欲しいカラムが増えた際に、pluck
メソッドの引数とmap
メソッドのブロック内の2ヶ所に変更を加える必要があります。
また、一時的に配列の添字を使っているので、何列目が何のカラムだったっけ?ってなりがちです。
そこで、Array#transpose
メソッドを使って改良してみましょう。
columns = [:id, :name]
users = User.pluck(*columns).map do |values|
[columns, [*values]].transpose.to_h
end
これでカラム名の指定箇所を一ヶ所に集約できました。
ハッシュに変換する処理もtranspose
メソッドで完結になっただけでなく、欲しいカラムが増えてもcolumns
の値を変更するだけでOKです。
[*values]
はcolumns
に指定したカラムが1つだけだった場合に対応するための記述です。
カラムが複数個になる場合は[columns, values]
と同義です。
transposeとto_hについて
transpose
メソッドが何やってるのか少しだけ解説します。( Array#transpose )transpose
は転置行列を生成するメソッドで、具体的には以下のような動作をします。
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9] ].transpose
# => [
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]]
# もう少しわかりやすくしてみる
[["■", "■", "■"],
["◯", "◯", "◯"],
["★", "★", "★"]].transpose
# => [
["■", "◯", "★"],
["■", "◯", "★"],
["■", "◯", "★"]]
# (単純な反時計回りじゃないです。上の例で1, 5, 9の場所が変わってないことに注目してください。)
こんな感じで行と列を入れ替えた行列を転置行列と言います。pluck
の例の時、どのような動作をしていたのかというと
[columns, [*values]].transpose.to_h
# columns => [:id, :name]
# [*values] => [1, "太郎"]
# [columns, [*values]] => [[:id, :name], [1, "太郎"]]
# [columns, [*values]].transpose => [[:id, 1], [:name, "太郎"]]
こんな感じで[カラム名, 値]
という配列がカラムの数だけ格納された配列が生成されてます。to_h
は、[カラム名, 値]
な配列から{ カラム名 => 値 }
というハッシュを生成するメソッドです。
こんな感じでpluck
によって生成された値の配列からハッシュを生成してました。
まとめ
columns = [:id, :name]
users = User.pluck(*columns).map do |values|
[columns, [*values]].transpose.to_h
end
ちなみに、User.column_names
を使うと全カラムが取得でき楽になります。
レスポンスに含めるべきでないカラムも除外できる上、DBにカラム追加しても変更点なし!
columns = User.column_names.map(&:to_sym) - [:password_digest, :secret_data]
pluck
メソッドでパフォーマンスを上げつつ、transpose
とto_h
を駆使してハッピーになりましょう!