LoginSignup
2
3

ActiveRecord オブジェクトでも values_at したい

Last updated at Posted at 2016-04-16

追記 (2023/06/29 (木))

Rails 6.1 以降では ActiveRecord::Core というメソッドが追加されています。

よって Rails 6.1 以降では以下で紹介している独自の拡張は不要です。

@jnchito さん、コメントで教えていただきありがとうございました ☺️

概要

僕の大好きなメソッドに Array#values_atHash#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]

これは

  1. attributes で ActiveRecord オブジェクトの属性をハッシュで受け取る。
  2. Hash オブジェクトのキーとして文字列とシンボルの両方を受け付けられるように ActiveSupport::HashWithIndifferentAccess オブジェクトに変換する。
  3. 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>
2
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3