onurozgurozkan
onurozgurozkan

Reputation: 1794

Rails Active Record Nested Attributes Validation which are in the same request

I have two models house and booking.Everything is okey over booking_date validation. But when I try to update or create multi booking in the same request. Validation can't check the invalid booking in the same request params.

Let give an example assume that booking table is empty.

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

Second booking also save but its start_date is between first booking interval. When I save them one by one validation works.

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

I google nearly 2 hours and could not find anything. What is the best approach to solve this problem?

Some resources

Upvotes: 0

Views: 655

Answers (1)

moonfly
moonfly

Reputation: 1820

This was only a quick 15-minutes research on my part, so I may be wrong, but I believe here's the root cause of your problem:

What accepts_nested_attributes_for does under the hood, it calls 'build' for new Booking objects (nothing is validated at this point, objects are created in memory, not stored to db) and registers validation and save hooks to be called when the parent object (House) is saved. So, in my understanding, all validations are first called for all created objects (by calling 'valid?' for each of them. Then, again if I get it right, they are saved using insert_record(record,false) which leads to save(:validate => false), so validations are not called for the 2nd time.

You can look at the sources inside these pages: http://apidock.com/rails/v3.2.8/ActiveRecord/AutosaveAssociation/save_collection_association, http://apidock.com/rails/ActiveRecord/Associations/HasAndBelongsToManyAssociation/insert_record

You validations call Booking.where(...) to find the overlapping dates-ranges. At this point the newly created Booking objects are still only in memory, not saved to the db (remember, we are just calling valid? for each of them in the loop, saves will be done later). Thus Booking.where(...) which runs a query against a DB doesn't find them there and returns nothing. Thus they all pass valid? stage and then saved.

In a nutshell, the records created together in such a way will not be cross-validated against each other (only against the previously existing records in the database). Hence the problem you see.

Thus either save them one-by-one, or check for such date-overlapping cases among the simultaneously created Bookings yourself before saving.

Upvotes: 2

Related Questions