'Custom validator with argument

I have the following class:

class PatientPaymentSpreadsheetRow < ApplicationSpreadsheetRow
  include ActiveModel::Validations
  validate :date_format

  def date_format
    unless value('Transaction').split('/').last.length == 4
      errors.add('Transaction', 'date format invalid')
    end
  end
end

This particular validation happens to act on value('Transaction'). I'd like my validator to be sufficiently generic that I can pass in any value (e.g. value('Date of birth')) and have it act on that value.

How can I accomplish this?



Solution 1:[1]

This is an old question, but I think this is more in the ballpark of what you were asking.

ActiveModel::EachValidator allows you to pass in options as a hash, which will be available in the class instance with options.

You can set up the validation in your model like

class SomeModel < ApplicationRecord
  validates :transaction, date_format: { value: 'Transaction' }
end

And the custom validator would look like:

class DateFormatValidator < ActiveModel::EachValidator
  def validate_each(model, attribute, value)
    return if options[:value].split('/').last.length == 4
    #         ^^^^^^^ hash you pass is here
    model.errors.add(attribute, 'date format invalid')
  end
end

Solution 2:[2]

But presumably you want the validations to run unconditionally, so...

validate :date_format
DATES_TO_VALIDATE = ['Transaction', 'Date of birth', 'Other date']

def date_format
  DATES_TO_VALIDATE.each do |key|
    unless value(key).split('/').last.length == 4
      errors.add(key, 'date format invalid')
    end
  end
end

This can be extracted to an each_validator as per Marek Lipka's answer, with a custom constant DATES_TO_VALIDATE for each model and access it in the validator as model.class::DATES_TO_VALIDATE

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 brainbag
Solution 2 SteveTurczyn