I have a Rails ActiveModel with two fields date_from
and date_to
and I want the model to be valid when (and only when)
- either of these fields or both are blank
date_from < date_to
In other words, the model should be invalid only when both fields are set but they're in the wrong order. In that case I also want both fields to be marked as invalid.
I tried with
validates :date_from, comparison: { less_than_or_equal_to: :date_to }, allow_blank: true
validates :date_to, comparison: { greater_than_or_equal_to: :date_from }, allow_blank: true
But that fails when exactly one of the fields is set with
#<ActiveModel::Error attribute=date_to, type=comparison of Date with nil failed, options={}>
How can I make the comparison validation pass when the referenced field is blank?
CodePudding user response:
It can be done with two separate validates
calls by adding a conditional check with if
option
validates :date_from,
comparison: { less_than_or_equal_to: :date_to },
allow_blank: true,
if: :date_to # make sure `date_to` is not `nil`
validates :date_to,
comparison: { greater_than_or_equal_to: :date_from },
allow_blank: true,
if: :date_from
This will skip these validations if one of the dates is nil
. When both dates are present it runs both validations and adds two separate errors, which may be not quite right, since it is essentially one error.
To make the intent of this validation more obvious, a validate
method is a better fit
validate :date_from_is_less_than_date_to
def date_from_is_less_than_date_to
return unless date_from && date_to # valid if date(s) are missing
unless date_from < date_to
errors.add(:base, 'Date range is invalid')
# NOTE: to add errors to show on form fields
# errors.add(:date_from, 'must come before Date to')
# errors.add(:date_to, 'must come after Date from')
# NOTE: to add errors only for date that changed
# requires ActiveModel::Dirty
# errors.add(:date_from, 'must come before Date to') if date_from_changed?
# errors.add(:date_to, 'must come after Date from') if date_to_changed?
end
end