Reputation:
I have two models: space and booking. Space has_many bookings and Booking has two date attributes: check_in and check_out.
Given a valid date range, I want to show all spaces available during this range
This is the view:
<%= form_tag spaces_path, method: :get do %>
<%= date_field_tag :query1,
params[:query1],
class: "form-control" %>
<%= date_field_tag :query2,
params[:query2],
class: "form-control" %>
<%= submit_tag "Search", class: "btn" %>
<% end %>
This is the SpaceController:
(...)
def index
if params[:query1].present? && params[:query2].present?
query1 = DateTime.parse(params[:query1])
query2 = DateTime.parse(params[:query2])
search = query1..query2
bookings = Booking.all
# returns the bookings that overlaps with the search
overlapping_bookings = bookings.select do |booking|
check_in = booking[:check_in]
check_out = booking[:check_out]
period = check_in..check_out
search.overlaps?(booking.period)
end
# returns the spaces_id of the bookings that overlaps
overlapping_space_ids = overlapping_bookings.select do |overlapping_booking|
overlapping_booking[:space_id]
end
# remove the duplicates
overlapping_space_ids.uniq!
# remove the spaces with bookings that overlap with the search
@spaces = Space.all.reject do |space|
overlapping_space_ids.include? space[:id]
end
else
@spaces = Space.all
end
end
(...)
I assume the root cause of my problem is that I'm treating the Active Record Query Object
as an array of hashes, not sure if it's correct. I made some researches on this but I haven't found any exhaustive answer.
Upvotes: 0
Views: 499
Reputation: 107107
I would add a scope to the Booking
model first:
# in app/models/booking.rb
scope :overlapping, ->(from, to) {
where(
"(check_in, check_out) OVERLAPS (?, ?)", from, to
)
}
and would then change the whole controller method to:
def index
@spaces = Space.all
if params[:query1].present? && params[:query2].present?
from = DateTime.parse(params[:query1])
to = DateTime.parse(params[:query2])
@space = @space.where.not(
id: Booking.select(:space_id).overlapping(from, to)
)
end
end
Upvotes: 0
Reputation: 186
Using an SQL subquery (in PostgreSQL for example) you would do this:
sql = <<SQL
SELECT *
FROM spaces
WHERE id in (
SELECT space_id
FROM bookings
WHERE
(check_in, check_out) OVERLAPS (:from, :to)
)
SQL;
Booking.find_by_sql([sql, {from: query1, to: query2})
Hope that helps :)
Upvotes: 1