这是一个自我回答。
我创建了一个自定义验证器,它继承了内置长度验证器和 覆盖 超类的两个方法,使验证器可以接受 lambda 作为值。这使得验证器可以在运行时callblock。
class MyLengthValidator < ActiveModel::Validations::LengthValidator
def check_validity!
keys = CHECKS.keys & options.keys
if keys.empty?
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
# !!!!! Here is the point that I've customized !!!!!
value = options[key].is_a?(Proc) ? options[key].call : options[key]
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
end
end
end
def validate_each(record, attribute, value)
value = tokenize(record, value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
CHECKS.each do |key, validity_check|
# !!!!! Here is the point that I've customized !!!!!
next unless check_value = options[key].is_a?(Proc) ? options[key].call : options[key]
if !value.nil? || skip_nil_check?(key)
next if value_length.send(validity_check, check_value)
end
errors_options[:count] = check_value
default_message = options[MESSAGES[key]]
errors_options[:message] ||= default_message if default_message
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
end
并将 lambda 作为自定义长度验证器选项的值传递:
validates :my_field, my_length: {
minimum: -> { Preference.int_value("my.minimum") },
maximum: -> { Preference.int_value("my.maximum") }
}
需要时可以参考这个已关闭的 PR:https://github.com/rails/rails/pull/21730/files