Your scope condition
...
.where.not(
'bookings.arrival_date <= ? AND
bookings.departure_date >= ? AND
bookings.status != ?',
arrival_date,
departure_date,
0
)
...
will generate a WHERE query like this:
...
WHERE NOT (bookings.arrival_date <= 'arrival_date'
AND bookings.departure_date >= 'departure_date'
AND bookings.status != 0)
which the same to
...
WHERE NOT bookings.arrival_date <= 'arrival_date'
OR NOT bookings.departure_date >= 'departure_date'
OR NOT bookings.status != 0
Or simpler:
...
WHERE bookings.arrival_date > 'arrival_date'
OR bookings.departure_date < 'departure_date'
OR bookings.status == 0
This, I guess, queries nearly all the data you have. Because this is logic operation, not filtering. You should remove .not and write it in positive:
...
.where(
'bookings.arrival_date > ? AND
bookings.departure_date < ? AND
bookings.status == ?',
arrival_date,
departure_date,
0
)
...
Does it express correct your business logic:~
Available bookings are bookings have:
- arrival date after
<arrival_date> and
- departure date before
<departure_date> and
- status is
0
It's a bit complex but you could read the logic out of query below:
# query
arrival_date = "2021-07-10 01:00:00"
departure_date = "2021-07-20 01:00:00"
# query
select_query = "offers.*, " +
"SUM(CASE WHEN arrival_date <= '#{arrival_date}' THEN 1 ELSE 0 END) AS arrival_date_bound, " +
"SUM(CASE WHEN departure_date >= '#{departure_date}' THEN 1 ELSE 0 END) AS departure_date_bound, " +
"COUNT(*) as total"
Offer.select(select_query)
.left_outer_joins(:bookings) # supported from rails 5+
.where.not(bookings: {status: 0})
.group("offers.id, offers.name")
.having("arrival_bound + departure_bound = 0 AND total > 0")
This query joins offers with bookings, and group by offer. For each offer, we count numbers of pending bookings has arrival_date before a specific arrival_date, called arrival_bound. And count numbers of pending bookings has departure_date after a specific departure_date. And just get offers don't have these kind of bookings.
I add an extra total condition to filter out offers haven't booking.
I tried it with tests below. Only o1 and o5 have all bookings, which have both arrival_date and departure_date within range t1->t4
t0 = Time.new(2021,7, 5, 1, 0,0)
t1 = Time.new(2021,7,10, 1, 0,0) # arrival_date to query
t2 = Time.new(2021,7,13, 1, 0,0)
t3 = Time.new(2021,7,18, 1, 0,0)
t4 = Time.new(2021,7,20, 1, 0,0) # departure_date to query
t5 = Time.new(2021,7,25, 1, 0,0)
# offers have 1 booking
o1 = Offer.create!(name: "test 1")
Booking.create!(offer: o1, arrival_date: t2, departure_date: t3, status: 1)
o2 = Offer.create!(name: "test 2")
Booking.create!(offer: o2, arrival_date: t0, departure_date: t3, status: 1)
o3 = Offer.create!(name: "test 3")
Booking.create!(offer: o3, arrival_date: t2, departure_date: t5, status: 1)
o4 = Offer.create!(name: "test 4")
Booking.create!(offer: o4, arrival_date: t0, departure_date: t5, status: 1)
# offers have more than 1 booking
o5 = Offer.create!(name: "test 5")
Booking.create!(offer: o5, arrival_date: t2, departure_date: t3, status: 1)
Booking.create!(offer: o5, arrival_date: t2, departure_date: t3, status: 1)
o6 = Offer.create!(name: "test 6")
Booking.create!(offer: o6, arrival_date: t0, departure_date: t5, status: 1)
Booking.create!(offer: o6, arrival_date: t0, departure_date: t5, status: 1)
o7 = Offer.create!(name: "test 7")
Booking.create!(offer: o7, arrival_date: t2, departure_date: t3, status: 1)
Booking.create!(offer: o7, arrival_date: t2, departure_date: t5, status: 1)
o8 = Offer.create!(name: "test 8")
Booking.create!(offer: o8, arrival_date: t2, departure_date: t3, status: 1)
Booking.create!(offer: o8, arrival_date: t0, departure_date: t3, status: 1)