Techioz Blog

過去の日時ではなく、現在の日時から日付と時刻の範囲を選択します

概要

RoRを勉強中です。ユーザーから日付を入力するという要件が 1 つありますが、常に現在の日付時刻または将来の日付である必要があります。入力では、ユーザーは開始日時と終了日時を入力しています。現在、以下の構文とヘルパークラスを使用して日付と時刻を選択しています。

<div class="field">
    <%= form.label :start_time %>
    <%= form.datetime_select :start_time %>
  </div>

現在のフォームに従って、ユーザーがフォームから選択できるものは次のとおりです。

ここでは、古い日付時刻ではなく、常に現在の日付時刻からになるように日付と時刻の範囲を選択したいと考えました。

ここでは、ユーザーから将来のイベント情報を取得してデータベースに保存したいと考えています。

以下はイベントモデルです。

class Event < ApplicationRecord
  validates :start_time, presence: true
  validates :end_time, presence: true
  validate :valid_start_and_end_time

  def start_time
    super || end_time
  end

  private

  def valid_start_and_end_time
    start_time <= end_time
  end
end

およびイベントコントローラー:

class EventsController < ApplicationController
 before_action :set_event only: [:show, :edit, :update, :destroy]

  def index
    @event = Event.all.where('start_time > ?', Time.now).sort_by(&:start_time).sort_by(&:title)
  end

  def create 
      @event = Event.new(event_params)
      @event.save
  end
  
  private
  
  def event_params 
    params.require(:event).permit(:title, :start_time, :end_time)
  end 
end

基本的に、ここでやりたいことは次のとおりです。 1. ユーザーに開始日時と終了日時を入力してもらいます。すべて将来のイベントにするか、少なくとも検証を追加する必要があります。 2. モデルでは、イベント インデックスを start_time で並べ替え、次にタイトルで並べ替えたいと考えています。

RoR での学習を強化できるように、最適なアプローチでどのように実行できるかを理解するのを誰かが助けてくれませんか。 ここで何か助けていただければ幸いです。

解決策

比較バリデータ (Rails 7 で追加) を使用して、start_time が過去であってはいけないことを検証できます。

validates :start_time, comparison: { greater_than_or_equal_to: Time.current }

比較バリデーターを使用して、end_time が start_time 以降でなければならないことを検証することもできます。

validates :end_time, comparison: { greater_than_or_equal_to: :start_time }

この 2 つの検証では、デフォルトで属性の存在もチェックされるため、start_time と end_time が過去ではなく両方とも存在すること、および end_time が start_time 以降であることを確認できます。

つまり、 validates :start_time、presence: true、validates :end_time、presence: true、およびカスタム validate :valid_start_and_end_time を削除できることを意味します。

次のように、start_time でイベントを並べ替え、次にタイトルでイベントを並べ替えることができます。

Event.order(start_time: :asc, title: :asc)

これは次の SQL クエリに変換されます。

SELECT
  events.*
FROM
  events
ORDER BY
  events.start_time ASC,
  events.title ASC

イベントが start_date (古いものから新しいもの) 順に並べられ、次にタイトル (アルファベット順) 順に表示されます。

Rails では、これらの一般的に使用されるクエリをスコープ内に置くのが非常に一般的です。これを次のように書きます。

scope :sorted, -> { order(start_time: :asc, title: :asc) }

このスコープを定義すると、Event.order(start_time: :asc, title: :asc) の代わりに Event.sorted を使用できます。

イベント コントローラーで where(‘start_time > ?’, Time.now) を使用しているため、そのスコープも必要だと思います。次のように定義できます。

scope :upcoming, -> { where('start_time > ?', Time.current) }

インデックス アクションは次のように書き換えることができます。

def index
  @events = Event.upcoming.sorted
end

これにより、次の SQL クエリが作成されます。

SELECT
  events.*
FROM
  events
WHERE
  (start_time > '2024-05-10 21:03:50.413480') -- the current time
ORDER BY
  events.start_time ASC,
  events.title ASC