🐈

デフォルトの時刻を定数化する時は気をつけよう

2025/02/11に公開

前提(フレームワーク)

Rails

やりたかっこと

レコード作成日時からxx日後を有効期限としてレコードに保存したい

起こった問題

「有効期限が常に同じ日付になる」という不具合が起こった

原因

原因はモデルで以下のように定義していた定数(デフォルト設定)のせい

class HogeModel < ApplicationRecord
  EXPIRED_AT = Time.zone.now + 30.days

  validates :expired_at, presence: true, default: -> { EXPIRED_AT }
end

理由

レコード作成時点からxx日後を有効期限として設定するため、その値(式)をEXPIRED_ATとして定数に切り出していました。しかしこれだと、EXPIRED_AT にはRailsが起動した時点の値を基準に30日後が入ります。そのため「有効期限が常に同じ日付になる」ということになるわけです。
そもそも「定数」に可変となる値を定数としていることに違和感を持たなければならなかったのですが....。

対応

普通にブロック内に書くようにしました。

class HogeModel < ApplicationRecord
  validates :expired_at, presence: true, default: -> { Time.zone.now + 30.days }
end

他でも色々使い回すために定数みたいに切り出しておきたい場合は、以下のようにクラスメソッドにすれば良いです。

class HogeModel < ApplicationRecord
  validates :expired_at, presence: true, default: -> { default_expired_at }

  def self.default_expired_at
    Time.zone.now + 30.days
  end
end

まとめ

当たり前だけど可変の値は定数にしない。
もしxx日を基準に開始、終了とかの時は定数にして良い。
(デフォルトでxx日後になるはずなのになんでそうならないんだ!ともがいてましたが、一旦気づくと当たり前すぎて恥ずかしかった)

Discussion