在相关模型中查找日期范围内的差距| Ruby on Rails

时间:2010-01-27 13:13:34

标签: mysql ruby-on-rails activerecord

鉴于以下模型:

class Room < ActiveRecord::Base  
  has_many :contracts
end

class Contracts < ActiveRecord::Base
  # columns
  # start_date Date
  # end_date Date

  belongs_to :room
end

一个房间的合同没有重叠。我的问题是,我如何能够找到合同之间的差距。一个例子:

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]

奖励回合可以搜索具有特定日期范围的差距,甚至可以更好地将所有差距作为日期范围以哈希或数组的形式获取。

gaps = Room.contract_gaps
gaps # {1 => [((1month.since+1.day)..(2.months.since-1.day))]}

我已经通过谷歌搜索并找到了Inverting Date Ranges。但我真的不是一个想法,我如何在这个具体案例中使用它。

如果有人有解决方案或一些有用的提示来解决这个问题,那将会很棒。

3 个答案:

答案 0 :(得分:2)

最快的方法是使用SQL查询查找间隙。然而,这将是复杂的并且不能很好地映射到ActiveRecord模型,因此我能想到的下一个最快的方法是按时间顺序对数据库中的合同进行排序并找出Ruby中的空白,迭代每个结果并将房间和日期附加到数组当你遇到它们时。

但是,如果您需要更快地获取间隙,或者您正在进行大量间隙操作,或者如果这些差距在某种意义上是“产品”的销售,那么您可能会因为不同的模型而变得更好。 Slot怎么样,这是最小可能的合同长度(例如,一个月)?在接下来的几年里,您将为每个房间和每个月创建插槽。每个都有slot.available == true开始,after_save模型的Contract回调在必要时设置available = false。通过此设置,您可以更轻松地定义差距查找器(Room.with_available_slots):

class Contract < ActiveRecord::Base
  has_many :slots
end

class Slot < ActiveRecord::Base
  belongs_to :room
  belongs_to :contract
end

class Room < ActiveRecord::Base
  has_many :slots
  has_many :contracts, :through => :slots

  named_scope :with_available_slots,
    :joins      => :slots,
    :conditions => {:slots => {:contract_id => nil}},
    :select     => "*, COUNT(*) AS num_slots",
    :group      => "room_id",
    :having     => "num_slots > 0"
end

此设计还有其他一些有用的功能,例如能够阻止预订某些日期,对特定插槽应用不同的定价等等。它不像您的设计那么干净,但根据我的经验,它可以更好地处理真实世界的数据,因为异常更容易处理。

答案 1 :(得分:0)

我认为您希望将此作为实例方法,找到第一个合同的开始与给定房间的最后一个合同的结束之间的差距。而对于“奖金”的回答是一种将它们全部收集起来的类方法。在这里,它们是一个辅助方法,用于获取一组合同的日期范围:

class Room < ActiveRecord::Base
  has_many :contracts

  # This method gets our date range
  def date_range contract_list=[]
    sorted = contract_list.sort{|a,b| a.start_date <=> b.start_date}
    Array(sorted.first.start_date..sorted.last.end_date
  end

  # This version runs when called on the class itself
  def contract_gaps
    room_hash = {}
    Room.all.each{|room| room_hash[room] = room.contract_gaps}

    room_hash
  end

  # This version runs when called on a single room
  def contract_gaps(start=nil, end=nil)
    all_dates = self.class.date_range self.contracts

    on_dates = []
    sorted_contracts.each{|c| on_dates << Array(c.start_date..c.end_date)}

    all_dates - on_dates
  end
end

你会这样称呼它:

room = Room.find(1)
room.contract_gaps

这将返回一个数组,其中包含合同涵盖的之间的所有日期。或者调用类方法来获取房间和范围的哈希值。

答案 2 :(得分:0)

如果你只需要有合同缺口的房间,你可以做这样的选择(未经测试):

SELECT rooms.*, count(*) AS count_contracts FROM rooms
  INNER JOIN contracts as c1 ON c1.room_id = rooms.id
  OUTER JOIN contracts as c2 ON c1.start_date = c2.end_date AND c2.room_id = rooms.id
WHERE c2.id IS NULL
GROUP BY rooms.id HAVING count_contracts > 1

基本上我们正在寻找具有多个合约的房间,这些房间没有与该房间另一个合约的end_date相同的start_date(假设start_date - end_date范围是包含 - 独占)。

要选择差距,我可能会做这样的事情(再次,未经测试):

contracts = room.contracts.all(:order => 'start_date ASC')
gaps = []
contracts[0..-2].each_with_index do |contract, idx|
  start_date = contract.end_date
  end_date = contracts[idx+1].start_date
  gaps << (start_date..end_date) if start_date != end_date
end