Railsのwill_save_change_toで値が変更されているか調べてvaldiateする
Webアプリでvalidationを書いていると、値が変更になった時にだけvalidationをかけたい場合があります。Railsではwill_save_change_to_#{attr_name}?
というメソッドが用意されています。#{attr_name}
の部分はModelの属性(カラム名)で置き換えます。
値が変更されているか調べてからvaldiateする
会議室予約アプリを例にして、具体的な使い方をみてみます。
会議室(meeting_room)は複数の会議予約(meeting)をもちます。会議予約は、開始時刻(start_time)と終了時刻(end_time)をもちます。
ルーティングはこちらです。
# routes.rb Rails.application.routes.draw do resources :meeting_rooms do resources :meetings end end
仕様
- 会議予約
- subjectは32文字以下で必須項目
- start_timeは必須項目
- end_timeは必須項目
- start_timeはend_timeより前であること
- 新規登録時、start_timeは現在時刻より後であること
- start_timeが変更された時、start_timeは現在時刻より後であること
コード
本題ではない方のvalidation
- 会議予約
- subjectは32文字以下で必須項目
- start_timeは必須項目
- end_timeは必須項目
- start_timeはend_timeより前であること
このへんは今回の本題ではないのでコードだけ載せておきます
class Meeting < ApplicationRecord belongs_to :meeting_room validates :subject, length: {maximum: 32}, presence: true validates :start_time, presence: true validates :end_time, presence: true validate :start_time_should_be_before_end_time private def start_time_should_be_before_end_time return unless start_time && end_time if start_time >= end_time errors.add(:start_time, 'は終了時間よりも前に設定してください') end end end
値の変更時にvalidationをかける
- 会議予約
- 新規登録時、start_timeは現在時刻より後であること
- start_timeが変更された時、start_timeは現在時刻より後であること
こちらを実装したコードは以下です。
class Meeting < ApplicationRecord # 略 validate :start_time_should_be_after_current, if: :new_or_start_time_changed private # 略 def new_or_start_time_changed start_time.present? && (new_record? || will_save_change_to_start_time?) end def start_time_should_be_after_current if start_time <= Time.zone.now errors.add(:start_time, 'は現在時刻よりも後に設定してください') end end end
new_or_start_time_changed
メソッドの中で、
will_save_change_to_start_time?
を使うことによって、start_timeが変更されたかどうかを判定しています。
start_time.present? && (new_record? || will_save_change_to_start_time?)
は、start_timeがnilでないかつ、新規またはstart_timeの値が変更されている、という意味です。
if: :new_or_start_time_changed
を外して、
class Meeting < ApplicationRecord # 略 validate :start_time_should_be_after_current private # 略 def start_time_should_be_after_current if start_time <= Time.zone.now errors.add(:start_time, 'は現在時刻よりも後に設定してください') end end end
と書いてしまうと、start_timeが過去になってしまうと、全ての項目(subjectなど)が更新できなくなってしまうので注意してください。
実行結果
新規登録時
※実行時は2018年8月10日
開始時間に過去の日時は登録できない↓
開始時間に未来の日時は登録できる↓
更新時
※実行時は2018年8月10日
開始時間が過去で登録されていて、日時が変更無しのとき(subjectのみ変更)は更新できる↓
開始時間が過去の場合、未来の日時へ変更できる↓
開始時間が過去の場合、別の過去の日時には変更できない↓
開始時間が未来の場合、 過去の日時には変更できない↓
開始時間が未来の場合、別の未来の日時には変更できる↓
環境
続く
こちらもどうぞ
参考
Rails4とRails5ではDBの値をとるメソッドや変更されたかを取得するメソッドの名前が変更されています。rail5の方がわかりやすいメソッド名ですね。#{attr_name}_in_database
と will_save_change_to_#{attr_name}?
以外のメソッドもこちらにまとまっています。