How to validate interdependent arguments?


#1

Hi,

I want to validate this:

params: start_date, end_date, expiry_date

  1. a start_date must either be before or on the same day as the end_date

  2. an end_date must either be after or on the same day as the start_date

  3. the expiry_date must be before the start_date

  4. all dates must be in the future

  5. if one date is present then all dates must be present:

valid:
  start   is present
  end     is present
  expiry  is present
    
valid:
  start   is not present
  end     is not present
  expiry  is not present
    
    
invalid:
  start   is not present
  end     is present
  expiry  is present

Otherwise dates do not need to be present.

I came up with the following:

schema = Dry::Validation.Schema do
  configure do
    def self.messages
      super.merge(
        errors: {
          dates: 'must be in the future!',
          _dates: 'must comply with (expiry < start <= end) !',
        }
      )
    end
  end

  required(:start_date) { filled? }
  required(:end_date) { filled? }
  required(:expiry_date) { filled? }

  validate(
    dates: %i[start_date end_date expiry_date]
  ) do |start_date, end_date, expiry_date|
    today = Date.today

    (start_date > today) & (end_date > today) & (expiry_date > today)
  end

  validate(
    _dates: %i[start_date end_date expiry_date]
  ) do |start, end_date, expiry_date|
      (start <= end_date) && (start > expiry_date)
  end
end

schema.call(date_params) if date_params.any?

What I am missing now is a way to provide error messages for each individual intrusion
e.g.
dates must not be in the past!
expiry_date should be before start_date!
start_date must not be after end_date!
end_date must not be before start_date!

I tried it with custom predicates, the only problem being that I want the predicates
only to be evaluated if the dates are filled otherwise not.

Any suggestions? Not sure if dry validation blocks are the best match for this :slight_smile: