Search code examples
ruby-on-railsrubyrubygems

select date and time range from current date time not from past date-time


I am working learning RoR. I have one requirement to input dates from user but it should always be current date time or future. In the input, user is entering start-datetime and end-datetime. Currently I using below syntax and helper class to select date and time.

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

As per current form, this is what user can select from form:

enter image description here

Here, I wanted to select date and time range in such a way that it is always from current date time, but not the old date time.

Here, I want to capture the future event information from user and store into database.

Below is the Event model:

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

and EventController:

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

Basically, what I want to do here is:
1. We want user to enter start date time and end date time, all should be future events or atleast add the validation.
2. In model, we want to sort the events index by start_time and then by title.

Can someone please help me to understand how it can be done in best approach so that I can enhance my learning in RoR.
Any help would be really appreciated here.


Solution

  • 1. Validations

    You can validate that start_time cannot be in the past with the comparison validator (added in Rails 7):

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

    You can also validate that end_time must be after or equal to start_time with the comparison validator:

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

    This two validations also check for the attribute presence by default, so with them you ensure that start_time and end_time are both present, not in the past, and that end_time is after or equal to start_time.

    This means you can remove validates :start_time, presence: true, validates :end_time, presence: true and your custom validate :valid_start_and_end_time.

    2. Scopes

    You can sort the events by start_time and then by title like so:

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

    which translates to this SQL query:

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

    and gives you the events ordered by start_date (oldest to newest) and then by title (alphabetically).

    In Rails, it's very common to have these commonly used queries in scopes. You would write this like so:

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

    With this scope defined, instead of Event.order(start_time: :asc, title: :asc), you can use Event.sorted.

    Since you're using where('start_time > ?', Time.now) in the events controller, I assume you also want a scope for that, which you can define like so:

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

    Your index action can now be rewritten as:

    def index
      @events = Event.upcoming.sorted
    end
    

    which in makes this SQL query:

    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