はじめに
実務に入ってもうすぐで1ヶ月が経とうとしていますが、巨大なアプリケーションに日々立ち向かったり、与えられた要件を何とか実装する日々を送っています。
今回は実務の中で出てきて勉強になった期間の重複に関するカスタムバリデーション
について自分の備忘録として書いていきます。
前提
- 例として、あるイベントの抽選期間を登録・管理するためのテーブルを作成している場合を想定する。
- 要件としては、抽選期間は被りがないようにしたい場合を想定する。
- 今回の実装はモデル周りだけであり、コントローラやビューは既に実装しているものとする。
- モデル名は
LotteryPeriod
モデルとする。
期間の重複検証ロジック
下記リンクで解説されているように、
比較開始日付 <= 対象終了日付 AND 比較終了日付 >= 対象開始日付
というとてもシンプルな表現で表せます。
カスタムバリデーションの作り方
今回はActiveModel::EachValidator
を利用した方法で実装していきます。
これはActiveRecord中にデフォルトではないバリデーションを自分で作りたい時などに使う手法です。
そして、何とたったの3ステップで実装できちゃいます。
1. カスタムバリデーション用ファイルの作成
% touch app/models/lottery_period_validator.rb
2. 作成したカスタムバリデーション用ファイルに実装
以下が目的の期間の重複を検証するためのバリデーションです。
今回はActiveModel::EachValidator
を利用し、validate_each
メソッド中に処理を記述していきました。
validate_each
メソッド中のrecord
、attribute
、value
はそれぞれ、以下を意味しています。
-
record
:検証対象のモデルオブジェクト(この例ではLotteryPeriod
モデル) -
attribute
:検証対象のフィールド名(この例ではlottery_period_start_at
やlottery_period_end_at
など) -
value
:検証対象の値(例えば、2019-03-31 14:00 00:00
などの具体的な値のこと)
カスタムバリデーションをより詳細に知りたい方は、モデルの属性に自作のバリデーションを追加するなど、先人達が書いてくださった記事が沢山ありますのでそちらをご参考下さい。
# 抽選期間に重複がないかを検証するバリデーター
class LotteryPeriodValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# 新規登録する期間
new_start_date = record.lottery_period_start_at
new_end_date = record.lottery_period_end_at
return unless new_start_date.present? && new_end_date.present?
# 重複する期間を検索(編集時は自期間を除いて検索)
if record.id.present?
not_own_periods = LotteryPeriod.where('id NOT IN (?) AND lottery_period_start_at <= ? AND lottery_period_end_at >= ?', record.id, new_end_date, new_start_date)
else
not_own_periods = LotteryPeriod.where('lottery_period_start_at <= ? AND lottery_period_end_at >= ?', new_end_date, new_start_date)
end
record.errors.add(attribute, 'に重複があります') if not_own_periods.present?
end
end
3. LotteryPeriod
モデルの中身に追記
上記で設定したクラス名(上記例ではlottery_period
)を用いてクラス名: true
と追加してあげるとそのバリデーションファイルを参照して検証が行われるようになります。
class LotteryPeriod < ActiveRecord::Base
・・・
# lottery_period: true と記述することで上記バリデーションを使えるようになります。
validates :lottery_period_start_at, lottery_period: true
validates :lottery_period_end_at, lottery_period: true
・・・
end
まとめ
-
期間の重複に関するカスタムバリデーション
の自作方法についてまとめた。 - 期間の重複条件はとてもシンプルに書けることを知ることができた。