LoginSignup
1
0

More than 5 years have passed since last update.

railsのserializeは値が同一でも設定し直すだけでchangedになるのをどうにかしたい

Posted at

そもそもで、性能を気にする場合にserializeを使うのか否かといった議論とか、そもそもserializeって何っていう話とかはQiitaにたくさん記事があるのでそちらも併せてごらんください。

ここでは、とにかくserializeしますってなった後の話をしたいと思います。
以下、サンプルです。税区分をデータベースでは整数で保持するけど、モデル上は文字列で取り扱う場合です。

module TaxType
  TAX_TYPE = [[1, '税別'], [2, '税込'], [3, '非課税'], [4, '不課税']]
  I2S = TAX_TYPE.to_h
  S2I = TAX_TYPE.map(&:reverse).to_h
  def self.dump(value)
    S2I[value]
  end
  def self.load(value)
    I2S[value]
  end
end

class Slip
  ...
  serialize :tax_type, TaxType
  ...
end

ここで、もともと税区分が'税別'だったときに'税別'を設定する場合、変更なしとして扱いたい。
changed? が false という評価になってほしいわけです。

slip.tax_type = '税別'

ですが実際は、changed? はtrueになってしまいます。
変更されたか否かにかかわらず、常にtrueです。中身を見ずにtrueを設定しています。

この設計といいますか、仕様の意図は、巨大なJSONオブジェクトが変更されたかどうかを計算しようとするととても時間がかかるので、変更されたかどうかを計算をしないと決める必要があった。
それであれば、保存しておくに越したことはない。
というところではないでしょうか。

今回は単に文字列ですから、比較のコストは大したことはありません。
ついでと言っては何ですが、そのようなカラムはほかにもあるかもしれませんので、

before_save do
  changes_to_save.each do |attribute, value|
    was, is = value
    if was == is
      clear_attribute_change attribute
    end
  end
  true
end

という感じで、保存の前に変更されているとマークされているもの全体に対して、実際はどうなんだ?ということを一つ一つチェックしていきます。

もしバリデーション中に変更されているかどうかの結果を使用したい場合は、この定義を before_save ではなく before_validation で行います。
保存のときだけなら、別にいつでも良いです。

せっかくなので、concernsに置けるようにしてみます。

module ClearChangesIfEqual
  extend ActiveSupport::Concern

  included do
    before_save do
      changes_to_save.each do |attribute, value|
        was, is = value
        if was == is
          clear_attribute_change attribute
        end
      end
      true
    end
  end
end

必要に応じてmodelにincludeして使います。

コード自体は大したことがないもので、べたに比較し続けるかんじです。
ですが、挙動のイメージがわかないと、どこで処理をするのが良いのか判断に迷いますね。

では、今日もここまで読んでくれてありがとう。

1
0
0

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
1
0