Taking some inspiration from the answer of SteveTurczyn. The following might give you some inspiration.
class Space < ApplicationRecord
# attributes: id
has_many :bookings
def self.available(period)
bookings = Booking.overlap(period)
where.not(id: bookings.select(:space_id))
end
def available?(period)
if bookings.loaded?
bookings.none? { |booking| booking.overlap?(period) }
else
bookings.overlap(period).none?
end
end
end
class Booking < ApplicationRecord
# attributes: id, space_id, start, end
belongs_to :space
def self.overlap(period)
period = FormatConverters.to_period(period)
# lteq = less than or equal to, gteq = greater than or equal to
# Other methods available on attributes can be found here:
# https://www.rubydoc.info/gems/arel/Arel/Attributes/Attribute
where(arel_table[:start].lteq(period.end).and(arel_table[:end].gteq(period.start)))
end
def overlap?(period)
period = FormatConverters.to_period(period)
self.start <= period.end && self.end >= period.start
end
module FormatConverters
module_function
def to_period(obj)
return obj if obj.respond_to?(:start) && obj.respond_to?(:end)
obj..obj
end
end
end
With the above implemented you can query a single space if it is available during a period:
from = Time.new(2019, 10, 1, 9, 30)
to = Time.new(2019, 10, 5, 17, 30)
period = from..to
space.available?(period) # true/false
You can get all spaces available:
spaces = Space.available(period) # all available spaces during the period
Note that class methods will also be available on the scope chain:
spaces = Space.scope_01.scope_02.available(period)
I've also added the overlap
scope and overlap?
helper to simplify creating the above helpers.
Since in my version Booking
has a start
and end
attribute (similar to Range
) you can also provide it to any methods accepting a period.
booking_01.overlap?(booking_02) # true/false
To retrieve all bookings that that overlap this very moment:
bookings = Booking.overlap(Time.now) # all bookings overlapping the period
Hope this gave you some inspiration. If you'd like to know how the overlap checking works I have to forward you to this question.
Note: This answer assumes that the provided period is valid. A.k.a. start
<= end
. If you for some reason provide Time.new(2019, 10, 1)..Time.new(2019, 9, 23)
the results are going to be skewed.