Home > Software design >  How to validate with comparison against a blank value?
How to validate with comparison against a blank value?

Time:04-13

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
  • Related