ActiveRecord の serializable_hash の使い方


動機

  • ロジックの分離、疎結合化を進めたかった。
    • 切り出し先ではデータベースアクセスを一切させない!
    • すべてのデータを引数渡し!
    • ステートレス!
    • テストしやすく!
    • 独立性を確保!
  • そのため、次の流れで進めることにした。
    1. 呼び出し元ですべてのデータ取得を事前に行う。
    2. 必要な全データをシリアライズして、切り出し先に引数で渡す。
    3. 受け取ったデータに対して必要な処理を行う。(データベースアクセスなし)
  • serializable_hashas_json の日本語情報があまりなく、備忘録として本記事を執筆しようと思った。

serializable_hash の使い方

特定の属性だけ、含めたいとき

selfActiveRecord オブジェクトであることが前提。

  self.serializable_hash(
    only: %w(name gender age)
  )

特定の属性を除きたいとき

  self.serializable_hash(
    except: %w(id created_at updated_at)
  )

特定のメソッドの実行結果を serialize 結果に含めたいとき

  def price_with_currency
    "%5.2f 円" % self.price
  end

  def serialize_for_discount_logic
    self.serializable_hash(
      except: %w(id created_at updated_at),
      methods: %w(price_with_currency)
    )
  end

Association 先を含めたいとき

class Order < ActiveRecord::Base
  belongs_to :customer

  def serialize_with_the_buyer_full
    self.serializable_hash(
      include: customer
    )
  end
end

Association 先でもシリアライズする属性を指定したいとき。

class Order < ActiveRecord::Base
  belongs_to :customer

  def serialize_with_the_buyer_partial
    self.serializable_hash(
      include: {
        customer: {
          except: %w(id created_at updated_at)
        }
      }
    )
  end
end

属性取得方法をカスタマイズしたいとき

read_attribute_for_serialization をオーバーライドすると、属性の取得方法をカスタマイズできる。
デフォルトは Object#sendattr_accessor を使っている場合など、通常の利用では、このオーバーライドは不要。

逆に getter を定義しない場合とかに便利。

person.rb
class Person
  include ActiveModel::Model
  include ActiveModel::Serialization

  def initialize(data = {})
    @data = data
  end

  def age= age
    @data["age"] = age
  end

  def read_attribute_for_serialization(key)
    @data[key]
  end

  def attributes
    @data.dup
  end
end

person = Person.new
person.age = 20

person.serializable_hash()

as_json の話

as_json メソンドは実は serializable_hash のうすいラッパーなので、上記で書いているのは、 as_json でもあてはまります。