Поиск пробелов в диапазонах дат в связанной модели | Рубин на рельсах - PullRequest
2 голосов
/ 27 января 2010

Учитывая следующие модели:

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))]}

Я уже искал через Google и нашел Инвертирование диапазонов дат . Но я, честно говоря, не совсем понимаю, как я могу использовать это в этом конкретном случае.

Было бы замечательно, если у кого-то есть решение или несколько полезных советов для решения этой проблемы.

Ответы [ 3 ]

2 голосов
/ 27 января 2010

Самый быстрый способ - найти пробелы с помощью 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

В этом дизайне есть и другие полезные функции, такие как возможность предотвращения бронирования определенных дат, применение разных цен к определенным слотам и т. Д. Он не такой чистый, как ваш дизайн, но, по моему опыту, он лучше работает с реальными данными, поскольку исключения более легко обрабатываются.

0 голосов
/ 27 января 2010

Если вам просто нужны комнаты с пробелами в контракте, вы можете сделать такой выбор (не тестировался):

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 является инклюзивным).

Чтобы выбрать только промежутки, я бы, вероятно, сделал что-то вроде этого (опять же, не проверял):

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
0 голосов
/ 27 января 2010

Я думаю, что вы хотите сделать это как метод экземпляра, чтобы найти разрывы между началом первого контракта и концом последнего контракта для данной комнаты. И в ответ на «бонус» есть метод класса, который собирает их всех. Вот они вместе с вспомогательным методом для получения диапазона дат для набора контрактов:

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

Возвращает массив со всеми датами между ними, которые не охватываются договором. Или вызовите метод класса, чтобы получить хэш комнат и диапазонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...