Łukasz Korol Łukasz Korol - 5 months ago 8
Ruby Question

How to prevent validation of self object

Hi I have a method that check overlapping for new and updated objects.
Method:

def timeline
if start_at_changed? || end_at_changed? || person_id_changed?
if (person.vacations.where('start_at <= ?', start_at).count > 0 &&
person.vacations.where('end_at >= ?', end_at).count > 0) ||
(person.vacations.where('start_at <= ?', start_at).count > 0 &&
person.vacations.where('end_at <= ?', end_at).count > 0 &&
person.vacations.where('end_at >= ?', start_at).count > 0) ||
(person.vacations.where('start_at >= ?', start_at).count > 0 &&
person.vacations.where('start_at <= ?', end_at).count > 0 &&
person.vacations.where('end_at >= ?', end_at).count > 0)
errors.add(:base, 'You have a vacation during this period.')
end
end
end


My issue is while I try edit some vacation. If I change e.g. start_at validation checks also this object and return error. Example: start_at 2016-06-27, end_at: 2016-06-30. I try change start_at to 2016-06-26. Validation returns 'You have a vacation during this period.', because it check period 2016-06-27 - 2016-06-30. How to exclude checking object that I try update, only other?

Answer

You just need to exclude the current vacation id on you queries. Something like:

def timeline
  if start_at_changed? || end_at_changed? || person_id_changed?
    if (person.vacations.where('start_at <= ? and vacations.id != ?', start_at, id).count > 0 &&
      person.vacations.where('end_at >= ? and vacations.id != ?', end_at, id).count > 0) ||
      ...
    end
  end
end

Also you can use the not condition. More info here and here.

Just to point, you're executing a lot of queries here. This can be very expensive in production. My suggestion is to merge all this queries in just one query, so besides the speed improvements, the readability of your code will be really better. You code will look like this:

def timeline
  if start_at_changed? || end_at_changed? || person_id_changed?
    overlapping_vacations = person.vacations.where('vacations.id != ?', id)
    overlapping_vacations.where('((start_at <= :start_at AND end_at >= :end_at) OR
                                 (start_at <= :start_at AND end_at <= :end_at AND end_at >= :start_at) OR
                                 (start_at >= :start_at AND start_at <= :end_at AND end_at >= :end_at))',
                                start_at: start_at, end_at: end_at)
    if overlapping_vacations.exists?
      errors.add(:base, 'You have a vacation during this period.')
    end
  end
end
Comments