module Formulario class Form def initialize(skip_validations: false, **params) __fields.each do |field_name, **options| send("#{field_name}=", params.delete(field_name)) end params.each do |k, v| send("#{k}=", v) end __validate! unless skip_validations end def self.default params = __fields.each_with_object({}) { |field, res| default_value = field.last[:default] res[field.first] = default_value.is_a?(Proc) ? nil : default_value } new(skip_validations: true, **params) end def __fields @__fields ||= {} end def params __fields.each_with_object({}) { |field_info, res| field_name = field_info.first options = field_info.last res[field_name] = options[:value].value } end def fields @fields ||= __fields.each_with_object({}) { |field, result| result[field.first] = field.last[:value] } end def valid? __fields.none? { |_, value:, **options| value.exceptional? } end def error_fields @error_fields ||= fields.select { |_, field| field.exceptional? } end def errors error_fields.transform_values(&:to_a) end def on_valid yield if valid? self end def on_invalid yield unless valid? self end private def self.__fields @field ||= {} end def __fields @__fields ||= self.class.__fields end def self.field(field_name, field_type=Field, default: Field.type_for(field_type).default) __fields[field_name.to_sym] = { default: default, validators: [], } define_method(field_name) do __fields.dig(field_name.to_sym, :value) end define_method("set_#{field_name}") do |raw_value| return raw_value unless Utils.empty?(raw_value) default.is_a?(Proc) ? instance_eval(&default) : default end define_method("#{field_name}=") do |raw_value| __fields[field_name.to_sym][:value] = Field.type_for(field_type).for( send("set_#{field_name}", raw_value) ) end end def self.validate(field_name, validator=Undefined, message: Undefined, &validation_block) validation_function = case when block_given? validation_block when validator.is_a?(Symbol) validator when validator.is_a?(::Formulario::Validator) validator end __fields[field_name][:validators] << { validator: validation_function, message: message, } end def __validate! __fields.each do |field_name, validators:, value:, **options| __fields[field_name][:value] = validators.inject(value) do |current_value, validator_hash| validator_block = case validator_hash[:validator] when ::Formulario::Validator validator_hash[:validator] when Symbol ::Formulario::Validator.new(&method(validator_hash[:validator])) when Proc ::Formulario::Validator.new(&validator_hash[:validator]) end if instance_exec(value: value.value, object: self, field_name: field_name, &validator_block.to_proc).valid? current_value else message = if validator_hash[:validator].is_a?(::Formulario::Validator) validator_hash[:validator].message end ::Formulario::Field::ExceptionalValue .new(current_value, reasons: message || validator_hash[:message]) end end end end end end