Making a reservation app. How can I check or validate the date for example date_start
and date_end
has the value of
date_start date_end 26-11-2017 27-11-2017
I will create another reservation which has these starting and end date values but it conflicts with the other values. How can I check for conflicting dates?
date_start date_end 25-11-2017 28-11-2017
Used this to validate the dates in between
Model
validate :no_reservation_overlap scope :overlapping, ->(period_start, period_end) do where "((date_start <= ?) and (date_end >= ?))", period_end, period_start end private def no_reservation_overlap if (Reservation.overlapping(date_start, date_end).any?) errors.add(:date_end, 'it overlaps another reservation') end end
View - choosing the date
<%= f.label :'date_start:' %> <%= f.date_field :date_start %> <%= f.label :'date_end:' %> <%= f.date_field :date_end %>
Sample dates 26 to 27 are already booked/reserved supposedly the it must prevent from inserting 25 to 28 because 26 to 27 are already booked.
3 Answers
Answers 1
Model:
class Reservation < ApplicationRecord validate :overlapping private def overlapping if Reservation.where('? < date_end and ? > date_start', self.date_start, self.date_end).any? errors.add(:date_end, 'it overlaps another') end end
Schema:
create_table "reservations", force: :cascade do |t| t.date "date_start" t.date "date_end" t.datetime "created_at", null: false t.datetime "updated_at", null: false end
Here is rails console
log after I tried to create (24 nov - 27 nov) and (25 nov - 27 nov) when there was (25 nov - 26 nov)
irb(main):003:0> Reservation.create date_start: Date.parse('25-11-2017'), date_end: Date.parse('26-11-2017') (0.2ms) BEGIN (0.6ms) SELECT COUNT(*) FROM "reservations" WHERE ('2017-11-25' date_start) SQL (0.6ms) INSERT INTO "reservations" ("date_start", "date_end", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["date_start", "2017-11-25"], ["date_end", "2017-11-26"], ["created_at", "2017-11-21 13:23:05.192276"], ["updated_at", "2017-11-21 13:23:05.192276"]] (10.7ms) COMMIT => # irb(main):004:0> Reservation.create date_start: Date.parse('24-11-2017'), date_end: Date.parse('27-11-2017') (0.1ms) BEGIN (0.2ms) SELECT COUNT(*) FROM "reservations" WHERE ('2017-11-24' date_start) (0.2ms) ROLLBACK => # irb(main):005:0> Reservation.create date_start: Date.parse('25-11-2017'), date_end: Date.parse('26-11-2017') (0.1ms) BEGIN (0.3ms) SELECT COUNT(*) FROM "reservations" WHERE ('2017-11-25' date_start) (0.1ms) ROLLBACK => #
There are rollbacks as expected.
Answers 2
There will be many dates on you reservations table. for example:
- reservation one: 7.days.from_now ~ 6.days.from_now
- reservation two: 3.days.from_now ~ 1.days.from_now
Which means you have to recognize the reserved date by each range.
- originally: 1~3, 6~7
I used a hash to index reserved dates, like this:
- not available dates (from now): 1, 2, 3, 6, 7
- also, you can create efficient index narrowed by searching (or scoping) only the target date_start and date_end or active reservations
Model:
class Reservation < ApplicationRecord validate :exclusive_reservation? def exclusive_reservation? result = true reserved = {} Reservation.pluck(:date_start, :date_end).each do |date_range| (date_range.first..date_range.second).each do |date| reserved[date] = true end end if reserved.has_key? self.date_start errors.add(:date_start, 'it overlaps another reservation') result = false end if reserved.has_key? self.date_end errors.add(:date_end, 'it overlaps another reservation') result = false end result end end
Answers 3
your validation is not working because you are checking that the new reservation dates are between the reservations dates already existing in the database. In the specific case you have mentioned, You need to check existing reservations dates are between dates in the new reservation dates.
Example mentioned in the question for reference:
Already existing reservation in the database
date_start date_end 26-11-2017 27-11-2017
New reservation:
date_start date_end 25-11-2017 28-11-2017
You need to check for the following cases:
Notation:
- R1 is the reservation already existing in the database
- R2 is the new reservation user is trying to add.
- | is representing the start date and the end date
Case1
|------R1------| ____|------R2------|
when R1 starts and ends before R2
Case2
______|------R1------| |------R2------|______
when R2 starts and ends before R1
Case3
___|------R1------|___ |---------R2---------|
When R2 contains R1. (The case you have mentioned in your question)
Case4
|---------R1---------| __|------R2------|___
When R1 contains R2. (This is the only case you have covered in your overlapping scope)
Disclaimer: The scope below is untested and might have some issues. (You can easily write a SQL query by covering all the cases above)
scope :overlapping, ->(period_start, period_end) do where( "(:period_start <= date_start AND :period_end < date_end AND :period_start > period_end) OR (:period_start >= date_start AND :period_end > date_end AND :period_start < date_end) OR (:period_start < date_start AND :period_end >= date_end) OR (:period_start >= date_start AND :period_end <= date_end)",period_start: period_start, period_end: period_end) end
0 comments:
Post a Comment