pluckメソッドがArrayのArrayではなくHashのArrayを返せるようにする

通好みのメソッドpluck

Railsでアプリケーションを組むと、ActiveRecordのインスタンス生成コストが勿体ないな、と感じることが多いと思います。

そんな時の強い味方がpluckメソッドで、愛用される方も多いと思います。

Rails4からは複数のカラムも指定できて、使い勝手が向上しました。 複数カラムを指定すると、値がArrayのArrayで返って来ます。

例を示すと、例えばこんなデータがあるとすると

  • employees
id name created_at
1 Taro 2014-08-01 01:00:05
2 Jiro 2014-08-01 02:00:06
3 Saburo 2014-08-01 03:00:07
Emplyee.pluck :id, :name
=> [[1, "Taro"],
 [2, "Jiro"],
 [3, "Saburo"]]

のように返ります。 これはこれで良いのですが、この結果をJSONで返したい時等は、できればHashのArray、つまり

Emplyee.hash_pluck :id, :name
=> [{:id=>1, :name=>"Taro"},
 {:id=>2, :name=>"Jiro"},
 {:id=>3, :name=>"Saburo"}]

のように返って欲しいです。 これを実現できるように、pluckメソッドに手を入れました。

ついでに、tableの物理column名と論理column名が異なることもあるだろうと、key名も指定できるようにしています。

前提条件

Rails4.1で動作を確認しました。

できあがったものがこちら

コードの細かい説明は省きます。短時間で書いた、と言い訳を先にしておきます。

module ActiveRecord::Calculations
  def pluck_with_keys *column_names
    unless (options = column_names.pop).is_a? Hash
      return pluck_without_keys *(column_names << options)
    end
    _pluck = pluck_without_keys *column_names
    case keys = options[:keys]
    when TrueClass
      _pluck.map{|obj| Hash[*column_names.zip(obj).flatten]}
    when Array
      _pluck.map{|obj| Hash[*keys.zip(obj).flatten]}
    else
      _pluck
    end
  end
  alias_method_chain :pluck, :keys
end

これをRAILS_ROOT/lib/extensions/calculations.rb等に保存して、RAILS_ROOT/libをautoload_pathに追加した上でinitializer辺りでこのファイルをrequireします。

使ってみる

既存のシステムにも適用し易いように、元のpluckメソッドの挙動になるべく影響無いようにしています。

元々pluckは可変長引数を取りますが、最後の引数がオリジナルのpluckが取らないHashが来たら動きが変わります。

最後の引数にkeys: trueとすると、指定したcolumn名をkeyにしてHashが帰り、keys: [:employee_id, :employee_name]のようにkey名の配列を渡すと、そのように良い感じにしてくれます。

実際の動作はこのような感じです。

Employee.pluck :id, :name
=> [[1, "Taro"],
 [2, "Jiro"],
 [3, "Saburo"]]

Employee.pluck :id, :name, keys: true
=> [{:id=>1, :name=>"Taro"},
 {:id=>2, :name=>"Jiro"},
 {:id=>3, :name=>"Saburo"}]

Employee.pluck :id, :name, keys: [:employee_id, :employee_name]
=> [{:employee_id =>1, :employee_name=>"Taro"},
 {:employee_id =>2, :employee_name=>"Jiro"},
 {:employee_id =>3, :employee_name=>"Saburo"}]

key名が足りないと、あるものしか返しません。

Employee.pluck :id, :name, keys: [:employee_id]
=> [{:employee_id =>1},
 {:employee_id =>2},
 {:employee_id =>3}]

狙った通りに出来ました。

gemにしようかな。