追記 (2023/06/29 (木))
Rails 6.1 以降では ActiveRecord::Core というメソッドが追加されています。
よって Rails 6.1 以降では以下で紹介している独自の拡張は不要です。
@jnchito さん、コメントで教えていただきありがとうございました ☺️
概要
僕の大好きなメソッドに Array#values_at や Hash#values_at というものがあります。Array オブジェクトや Hash オブジェクトから任意のインデックス群、またはキー群に該当する値を取得するメソッドです。
['金剛', '比叡', '榛名', '霧島'].values_at(1, 3)
#=> ["比叡", "霧島"]
{ kongo: '金剛', hiei: '比叡', haruna: '榛名', kirishima: '霧島' }.values_at(:hiei, :kirishima)
#=> ["比叡", "霧島"]
今回、ActiveRecord オブジェクトでも同様のことがしたいなと思いました。
結論
ActiveRecord::Core#slice を使って実現しました。
module ActiveRecord
module Core
module Extension
def values_at(*methods)
slice(*methods).values
end
end
end
end
以下は使用例です。
class Category < ActiveRecord::Base
has_many :kanmusus
end
class Kanmusu < ActiveRecord::Base
include ActiveRecord::Core::Extension
belongs_to :category
end
Kanmusu.find(21).values_at(:name, :category, :length)
#=> ['金剛', #<Category ..., name: "金剛型戦艦", ...>, 222]
解説
初めは以下のように実装しました。
module ActiveRecord
module Core
module Extension
def old_values_at(*keys)
attributes
.with_indifferent_access
.values_at(*keys)
end
end
end
end
Kanmusu.find(21).old_values_at(:name, :length)
#=> ['金剛', 222]
これは
-
attributes
で ActiveRecord オブジェクトの属性をハッシュで受け取る。 - Hash オブジェクトのキーとして文字列とシンボルの両方を受け付けられるように ActiveSupport::HashWithIndifferentAccess オブジェクトに変換する。
- Hash#values_at を呼ぶ。
という流れです。
しかしこの方法では ActiveRecord オブジェクトの属性値しか取得できません。つまり、アソシエーション先のオブジェクトや任意のメソッドの返り値を取得することができないのです。ちなみに Hash#values_at
で Hash オブジェクトに存在しないキーを指定すると nil
が返ります。
Kanmusu.find(21).old_values_at(:name, :category, :length)
#=> ['金剛', nil, 222]
そこで ActiveRecord::Core#slice を使うことにしました。この内部では指定したメソッドのコールが行われているので、属性値以外も取得できます。さらに気の利いたことに、返り値は Hash オブジェクトではなく ActiveSupport::HashWithIndifferentAccess オブジェクトです。
Kanmusu.find(21).values_at('name', 'category', :length)
#=> ['金剛', #<Category ..., name: "金剛型戦艦", ...>, 222]
ただし ActiveRecord::Core#slice を使う場合、存在しないメソッドを指定するとエラーになるので注意が必要です。
Kanmusu.find(21).values_at(:name, :category, :length, :gouchin)
#=> NoMethodError: undefined method `gouchin' for #<Kanmusu>