lib/formtastic/inputs/base/validations.rb
# frozen_string_literal: true
module Formtastic
module Inputs
module Base
module Validations
class IndeterminableMinimumAttributeError < ArgumentError
def message
[
"A minimum value can not be determined when the validation uses :greater_than on a :decimal or :float column type.",
"Please alter the validation to use :greater_than_or_equal_to, or provide a value for this attribute explicitly with the :min option on input()."
].join("\n")
end
end
class IndeterminableMaximumAttributeError < ArgumentError
def message
[
"A maximum value can not be determined when the validation uses :less_than on a :decimal or :float column type.",
"Please alter the validation to use :less_than_or_equal_to, or provide a value for this attribute explicitly with the :max option on input()."
].join("\n")
end
end
def validations
@validations ||= if object && object.class.respond_to?(:validators_on)
object.class.validators_on(attributized_method_name).select do |validator|
validator_relevant?(validator)
end
else
nil
end
end
def validator_relevant?(validator)
return true unless validator.options.key?(:if) || validator.options.key?(:unless)
conditional = validator.options.key?(:if) ? validator.options[:if] : validator.options[:unless]
result = if conditional.respond_to?(:call) && conditional.arity > 0
conditional.call(object)
elsif conditional.respond_to?(:call) && conditional.arity == 0
object.instance_exec(&conditional)
elsif conditional.is_a?(::Symbol) && object.respond_to?(conditional)
object.send(conditional)
else
conditional
end
result = validator.options.key?(:unless) ? !result : !!result
not_required_through_negated_validation! if !result && [:presence, :inclusion, :length].include?(validator.kind)
result
end
def validation_limit
validation = validations? && validations.find do |validation|
validation.kind == :length
end
if validation
validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
else
nil
end
end
# Prefer :greater_than_or_equal_to over :greater_than, for no particular reason.
def validation_min
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation
# We can't determine an appropriate value for :greater_than with a float/decimal column
raise IndeterminableMinimumAttributeError if validation.options[:greater_than] && column? && [:float, :decimal].include?(column.type)
if validation.options[:greater_than_or_equal_to]
return option_value(validation.options[:greater_than_or_equal_to], object)
end
if validation.options[:greater_than]
return option_value(validation.options[:greater_than], object) + 1
end
end
end
# Prefer :less_than_or_equal_to over :less_than, for no particular reason.
def validation_max
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation
# We can't determine an appropriate value for :greater_than with a float/decimal column
raise IndeterminableMaximumAttributeError if validation.options[:less_than] && column? && [:float, :decimal].include?(column.type)
if validation.options[:less_than_or_equal_to]
return option_value(validation.options[:less_than_or_equal_to], object)
end
if validation.options[:less_than]
return option_value(validation.options[:less_than], object) - 1
end
end
end
def validation_step
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation
validation.options[:step] || (1 if validation_integer_only?)
else
nil
end
end
def validation_integer_only?
validation = validations? && validations.find do |validation|
validation.kind == :numericality
end
if validation
validation.options[:only_integer]
else
false
end
end
def validations?
validations != nil
end
def required?
return false if options[:required] == false
return true if options[:required] == true
return false if not_required_through_negated_validation?
if validations?
validations.any? { |validator|
if validator.options.key?(:on)
validator_on = Array(validator.options[:on])
next false if (validator_on.exclude?(:save)) && ((object.new_record? && validator_on.exclude?(:create)) || (!object.new_record? && validator_on.exclude?(:update)))
end
case validator.kind
when :presence
true
when :inclusion
validator.options[:allow_blank] != true
when :length
validator.options[:allow_blank] != true &&
validator.options[:minimum].to_i > 0 ||
validator.options[:within].try(:first).to_i > 0
else
false
end
}
else
return responds_to_global_required? && !!builder.all_fields_required_by_default
end
end
def required_attribute?
required? && builder.use_required_attribute
end
def not_required_through_negated_validation?
@not_required_through_negated_validation
end
def not_required_through_negated_validation!
@not_required_through_negated_validation = true
end
def responds_to_global_required?
true
end
def optional?
!required?
end
def autofocus?
opt_autofocus = options[:input_html] && options[:input_html][:autofocus]
!!opt_autofocus
end
def column_limit
column.limit if column? && column.respond_to?(:limit)
end
def limit
validation_limit || column_limit
end
def readonly?
readonly_from_options? || readonly_attribute?
end
def readonly_attribute?
object_class = self.object.class
object_class.respond_to?(:readonly_attributes) &&
self.object.persisted? &&
column.respond_to?(:name) &&
object_class.readonly_attributes.include?(column.name.to_s)
end
def readonly_from_options?
options[:input_html] && options[:input_html][:readonly]
end
private
# Loosely based on
# https://github.com/rails/rails/blob/459e7cf62252558bbf65f582a230562ab1a76c5e/activemodel/lib/active_model/validations/numericality.rb#L65-L70
def option_value(option, object)
case option
when Symbol
object.send(option)
when Proc
option.call(object)
else
option
end
end
end
end
end
end