【问题标题】:Finding gaps within date-ranges in an associated model | Ruby on Rails在关联模型中查找日期范围内的空白 | Ruby on Rails
【发布时间】:2010-01-27 13:13:34
【问题描述】:

鉴于以下模型:

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。但老实说,我真的不知道如何在这种特定情况下使用它。

如果有人有解决方案或一些有用的提示来解决这个问题,那就太好了。

【问题讨论】:

    标签: mysql ruby-on-rails activerecord


    【解决方案1】:

    最快的方法是使用 SQL 查询来查找差距。但是,这会很复杂,并且不能很好地映射到 ActiveRecord 模型,所以我能想到的下一个最快的方法是在数据库中按时间顺序对合同进行排序,并在 Ruby 中查找差距,遍历每个结果并将房间和日期附加到数组中当你遇到它们时。

    但是,如果您需要更快地访问间隙,或者您正在进行大量间隙操作,或者如果这些间隙在某种意义上是正在销售的“产品”,那么您最好使用不同的模型。 Slot 怎么样?这是可能的最短合同期限(例如,一个月)?在接下来的几年中,您将为每个房间和每个月创建插槽。每个都有slot.available == true 启动,Contract 模型的after_save 回调在必要时设置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
    

    此设计具有其他有用的功能,例如能够阻止预订某些日期、对某些时段应用不同的定价等。它不像您的设计那样干净,但根据我的经验,它更适用于真实世界的数据,因为例外情况更容易处理。

    【讨论】:

    • 嗨,亚历克斯,感谢您的回复。我喜欢你对插槽的想法。你有一个快速的例子,你会如何找到例如没有迭代所有结果的连续 3 个可用插槽?
    【解决方案2】:

    如果您只需要有合同空缺的房间,您可以进行这样的选择(未测试):

    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
    

    基本上,我们正在寻找具有多个合同的房间,这些合同的 start_date 与该房间的另一个合同的 end_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
    

    【讨论】:

      【解决方案3】:

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

      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
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多