Given the following models:
class Room < ActiveRecord::Base
has_many :contracts
end
class Contracts < ActiveRecord::Base
# columns
# start_date Date
# end_date Date
belongs_to :room
end
The contracts of one room are not overlapping. My question is, how i am be able to find gaps between contracts. An Example:
room = Room.create
c1 = room.contracts.create(:start_date => Date.today, :end_date => 1.month.since)
c2 = room.contracts.create(:start_date => 2.months.since, :end_date => 4.months.since)
rooms = Room.with_contract_gaps #rooms == [room]
The bonus round would be the possibility to search for gaps with a specific date-range or even better to fetch all gaps as date-ranges in as hash or array.
gaps = Room.contract_gaps
gaps # {1 => [((1month.since+1.day)..(2.months.since-1.day))]}
I have already searched via google and found Inverting Date Ranges. But i have honestly not really an idea, how i can use it in this specific case.
It would be great if someone has a solution or some helpful tips to solve this problem.
The fastest method would be to find gaps using an SQL query. However, this would be complicated and not map well to an ActiveRecord model, so the next fastest way I can think of is to sort contracts chronologically in the database and find gaps in Ruby, iterating through each result and appending rooms and dates to an array as you encounter them.
However, if you need faster access to gaps, or you’re doing a lot of gap manipulation, or if these gaps are in a sense the “product” being sold, you might be better off with a different model. What about a
Slot, which is the minimum possible contract length (eg, one month)? You’d create slots for every room and every month for the next few years. Each hasslot.available == trueto start, and theafter_savecallback of theContractmodel setsavailable = falsewhere necessary. With this setup you could define your gap-finder (Room.with_available_slots) more easily:This design has other useful features like the ability to prevent booking of certain dates, apply different pricing to certain slots, etc. It’s not as clean as your design, but in my experience it works better with real-world data because exceptions are more easily handled.