Проверка вложенных атрибутов Rails Active Record, которые находятся в одном запросе

У меня есть две модели дома и бронирования. Все в порядке с проверкой booking_date. Но когда я пытаюсь обновить или создать мультибронирование в том же запросе. Проверка не может проверить недействительное бронирование в тех же параметрах запроса.

Приведем пример, предположим, что таблица бронирования пуста.

params = { :house => {
  :title => 'joe', :booking_attributes => [
    { :start_date => '2012-01-01', :finish_date => '2012-01-30 },
    { :start_date => '2012-01-15', :finish_date => '2012-02-15 }
  ]
}}

Второе бронирование также сохраняется, но его start_date находится между первым интервалом бронирования. Когда я сохраняю их один за другим, проверка работает.

class House < ActiveRecord::Base
  attr_accessible :title, :booking_attributes
  has_many :booking
  accepts_nested_attributes_for :booking, reject_if: :all_blank, allow_destroy: true
end

class Booking < ActiveRecord::Base
  belongs_to :house
  attr_accessible :start_date, :finish_date

  validate :booking_date
  def booking_date

    # Validate start_date
    if Booking.where('start_date <= ? AND finish_date >= ? AND house_id = ?',
      self.start_date, self.start_date, self.house_id).exists?
      errors.add(:start_date, 'There is an other booking for this interval')
    end

    # Validate finish_date
    if Booking.where('start_date <= ? AND finish_date >= ? AND house_id = ?',
      self.finish_date, self.finish_date, self.house_id).exists?
      errors.add(:finish_date, 'There is an other booking for this interval')
    end
  end
end

Я гуглил почти 2 часа и ничего не мог найти. Каков наилучший подход к решению этой проблемы?

Некоторые ресурсы


person onurozgurozkan    schedule 27.02.2013    source источник


Ответы (1)


Это было всего лишь быстрое 15-минутное исследование с моей стороны, поэтому я могу ошибаться, но я считаю, что вот основная причина вашей проблемы:

Что accepts_nested_attributes_for делает под капотом, так это вызывает 'build' для новых объектов Booking (на этом этапе ничего не проверяется, объекты создаются в памяти, а не сохраняются в db) и регистрирует хуки проверки и сохранения, которые будут вызываться, когда родительский объект (House ) сохраняется. Итак, в моем понимании, все валидации сначала вызываются для всех созданных объектов (путем вызова 'valid?' для каждого из них. Затем, опять же если я правильно понял, они сохраняются с помощью insert_record(record ,false), что приводит к save(:validate => false), поэтому проверки не вызываются во второй раз.

Вы можете посмотреть исходники на этих страницах: http://apidock.com/rails/v3.2.8/ActiveRecord/AutosaveAssociation/save_collection_association, http://apidock.com/rails/ActiveRecord/Associations/HasAndBelongsToManyAssociation/insert_record

Вы вызываете проверку Booking.where(...), чтобы найти перекрывающиеся диапазоны дат. На данный момент вновь созданные объекты Booking все еще находятся только в памяти, а не сохраняются в базе данных (помните, мы просто вызываем valid? для каждого из них в цикле, сохранение будет выполнено позже). Таким образом, Booking.where(...), который выполняет запрос к БД, не находит их там и ничего не возвращает. Таким образом, они все проходят действительными? этапе, а затем сохраняется.

Короче говоря, записи, созданные вместе таким образом, не будут проходить перекрестную проверку друг против друга (только против ранее существовавших записей в базе данных). Отсюда проблема, которую вы видите.

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

person moonfly    schedule 28.02.2013
comment
Большое спасибо за ваш ответ. Я думаю, что лучшим решением является сохранение одного за другим по разным запросам. - person onurozgurozkan; 28.02.2013