【Rails】1日に記録できる数値の合計を制限するバリデーション


はじめに

投稿3回目です。
文章力が赤ちゃんレベルかつ遅筆なのでまだ慣れないです...
間違い等あればご指摘よろしくお願いします。

環境

  • Ruby 2.6.3
  • Rails 5.2.4

背景/目的

  • ポートフォリオで学習内容を記録するサイトを開発中。
  • 学習内容は1日に何回でも記録可能であり、項目には学習時間がある。
  • 学習時間の合計値が24時間/1日を超えないようにバリデーションを設定する。
  • 学習内容(learningsテーブル)の詳細は下記の通り。
カラム名 データ型 説明
date date 学習日
time float 学習時間

結論

app/models/learning.rb
class Learning < ApplicationRecord
  # 一つのユーザー(user)に対して、複数の学習記録(learning)が結びついている
  belongs_to :user

  # validateに定義したメソッドを設定
  validate :total_time_cannot_exceed_limit_time, on: :create
  validate :total_time_cannot_exceed_limit_time_for_edit, on: :update

  # 1日に記録できる学習時間の合計
  LIMIT_TIME_HOUR = 24

  # 入力された学習日に既に記録されている学習時間の合計値を取得する
  def one_day_time_sum(date)
    user.learnings.where(date: date).sum(:time)
  end

  # 入力された学習日に既に記録されている「編集対象の投稿以外の」学習時間の合計値を取得する
  def one_day_time_sum_unless_target_date(date)
    user.learnings.where(date: date).where.not(id: id).sum(:time)
  end

  # create時のカスタムバリデーション用のメソッドを定義
  def total_time_cannot_exceed_limit_time
    # [学習日が入力済]かつ[学習時間が入力済]かつ[学習日に既に記録された学習時間と入力した学習時間の合計が24時間を超える場合]
    if date.presence && time.presence && one_day_time_sum(date) + time > LIMIT_TIME_HOUR
      # エラーメッセージを表示する
      errors.add(:date, ":#{date.strftime("%Y年%m月%d日")}の学習時間の合計が#{LIMIT_TIME_HOUR}時間を超えています")
    end
  end

  # update時のカスタムバリデーション用のメソッドを定義
  def total_time_cannot_exceed_limit_time_for_edit
    # [学習日が入力済]かつ[学習時間が入力済]かつ[学習日に既に記録された「編集対象の投稿以外の」学習時間と入力した学習時間の合計が24時間を超える場合]
    if date.presence && time.presence && one_day_time_sum_unless_target_date(date) + time > LIMIT_TIME_HOUR
      errors.add(:date, ":#{date.strftime("%Y年%m月%d日")}の学習時間の合計が#{LIMIT_TIME_HOUR}時間を超えています")
    end
  end
end

createとupdateで処理を分けています。
コメントで処理の内容を記載しましたが、自分で読んでても正直よく分からないです。
例えば、下記のデータが既に保存されており、学習日2021-01-01・学習時間4の投稿を新たにする場合、

id 学習日 学習時間
1 2021-01-01 10.6
2 2021-01-01 11.4

入力された学習日2021-01-01に既に記録されている学習時間の合計値22時間を取得し、入力した学習時間4時間を足して、24時間と比較します。=> この場合26時間なのでエラーメッセージが表示されます。

これをupdateでも同じ処理にすると動作がおかしくなります。
例えば、id:1の学習時間を5時間に変更する場合、5 + 11.4 = 16.4時間になるのが理想です。
しかし、実際は10.6 + 11.4 + 5 = 27時間でNGになってしまいます。
user.learnings.where(date: date).sum(:time)だと編集前の学習時間も取得してしまうので、update時はwhere.not(id: id)を追記し、編集対象の投稿以外の学習時間の合計を取得するようにしています。

参考にさせていただきました

ここまで見て頂きありがとうございました。
ネーミングセンスがないのは許していただけると助かります😰