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:
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.
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
.
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