I have an Event model
that has form
time and to
time in my schedule app and I want to validate the overlapping time before saving.
Well, if you need a server side validation, you could implement some custom
validators in your model class:
validate :cannot_overlap_another_event
Next you need to code this method yourself:
def cannot_overlap_another_event
range = Range.new from, to
overlaps = Appointment.exclude_self(id).in_range(range)
overlap_error unless overlaps.empty?
end
Explaining what this code do, you create a Range
object with your from
and to
dates. Then it uses the helper scopes to exclude the Event
itself and check to see if there's an event in this range.
scope :in_range, -> range {
where('(from BETWEEN ? AND ?)', range.first, range.last)
}
scope :exclude_self, -> id { where.not(id: id) }
The overlap_error
is a method that populates the model's error hash to display on screen:
def overlap_error
errors.add(:overlap_error, 'There is already an event scheduled in this hour!')
end
I had the same problem for a project, my solution is to include a validation method inside the model, so I have a model for vacation periods taken and I must add periods (lapses) taking care that those lapses are not overlapping; Suppose you have a collection of lapses taken (active_permits) and you just got a new lapse from your form (lapse) then there are only two fundamental rules that will determine if the two periods are overlapping: lapse[:end] must be smaller (that is occurs earlier) than active_permit[:start] OR lapse[:start] must be greater (occurs later) than active_permit[:end], so UNLESS one of these rules are met, the two periods overlap. Code must be something like this:
def lapse_validation(lapse)
error = 0
@active_permits.each do |active_permit|
unless (lapse[:end] < active_permit[:start] || lapse[:start] > active_permit[:end])
error = 1
errors.add(:new_permit, ": The dates you entered overlapp an existing leave period")
end
end
return error
end
You can find a more detailed discussion on overlapping periods here: https://www.soliantconsulting.com/blog/determining-two-date-ranges-overlap/
In your EventsController#Create
& EventsController#Update
methods check for new time in between the old times like :
events = Event.where(:departure_date => event_params[:departure_date])
events.each do |event|
if event_params[:to].between?(event.to, event.from) || event_params[:from].between?(event.to, event.from)
flash[:error] = "Current Slot Time is already taken!"
redirect_to :back
return
end
end
Take a look at this https://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails. The key is to define one scope to chek for overlapping siblings. Somthing like this:
# Return a scope for all interval overlapping the given interval, including the given interval itself
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
Also you can check this gem: https://github.com/robinbortlik/validates_overlap I think it can help.