module Interactor::Validation::Validates::InstanceMethods
def self.prepended(base)
def self.prepended(base) # Include all validator modules base.include(Validators::Presence) base.include(Validators::Numeric) base.include(Validators::Boolean) base.include(Validators::Format) base.include(Validators::Length) base.include(Validators::Inclusion) base.include(Validators::Hash) base.include(Validators::Array) end
def errors
def errors @errors ||= Errors.new(halt_checker: -> { validation_config(:halt) }) end
def format_errors
def format_errors case validation_config(:mode) when :code format_errors_as_code else format_errors_as_default end end
def format_errors_as_code
def format_errors_as_code errors.map do |err| { code: generate_error_code(err.attribute, err.type) } end end
def format_errors_as_default
def format_errors_as_default errors.map do |err| { attribute: err.attribute, type: err.type, message: "#{err.attribute.to_s.humanize} #{err.message}" } end end
def generate_error_code(attribute, type)
def generate_error_code(attribute, type) # Convert attribute to uppercase with underscores # Handle nested attributes: user.email → USER_EMAIL, items[0].name → ITEMS[0]_NAME code_attribute = attribute.to_s .gsub(/\[(\d+)\]\./, '[\\1]_') .gsub(".", "_") .upcase # Use "IS_REQUIRED" for blank errors, otherwise use type name code_type = type == :blank ? "IS_REQUIRED" : type.to_s.upcase "#{code_attribute}_#{code_type}" end
def run_validations!
def run_validations! param_errors = false begin # Run parameter validations if self.class._validations self.class._validations.each do |param, rules| value = context.respond_to?(param) ? context.public_send(param) : nil validate_param(param, value, rules) end param_errors = errors.any? end # Run custom validations if defined # Skip if param validations failed and skip_validate is true validate! if respond_to?(:validate!, true) && !(param_errors && validation_config(:skip_validate)) rescue HaltValidation # Validation halted on first error - fall through to fail context end # Fail context if any errors exist context.fail!(errors: format_errors) if errors.any? end
def validate_nested(param, value, nested_rules)
def validate_nested(param, value, nested_rules) if value.is_a?(::Array) validate_array(param, value, nested_rules) elsif value.is_a?(::Hash) validate_hash(param, value, nested_rules) end end
def validate_param(param, value, rules)
def validate_param(param, value, rules) # Handle nested validation if rules[:_nested] validate_presence(param, value, rules[:presence]) if rules[:presence] validate_nested(param, value, rules[:_nested]) if value.present? return end # Standard validations validate_presence(param, value, rules[:presence]) if rules[:presence] return unless value.present? validate_boolean(param, value) if rules[:boolean] validate_format(param, value, rules[:format]) if rules[:format] validate_length(param, value, rules[:length]) if rules[:length] validate_inclusion(param, value, rules[:inclusion]) if rules[:inclusion] validate_numeric(param, value, rules[:numeric] || rules[:numericality]) if rules[:numeric] || rules[:numericality] end
def validation_config(key)
def validation_config(key) # Check per-interactor config first, then fall back to global config self.class._validation_config.key?(key) ? self.class._validation_config[key] : Interactor::Validation.configuration.public_send(key) end