module Formulario class Form attr_accessor :model def initialize(skip_validations: false, **params) self.class.__fields.each do |field_name, **options| send("#{field_name}=", params.delete(field_name)) end params.each do |k, v| if self.respond_to?("#{k}=") send("#{k}=", v) end end __validate! unless skip_validations end def self.for(model, **rest) if model fields = __fields.keys.each_with_object({}) { |field_name, res| res[field_name] = model.send(field_name) } new(**fields.merge({model: model, **rest})) else new(**rest) end 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 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? { |_, field| field.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 __fields @__fields ||= {} end def self.__fields @field ||= {} end def self.field(field_name, field_type=Field, default: Field.type_for(field_type).default) __fields[field_name.to_sym] = Field::UnvalidatedField.new( field_name: field_name, field_type: Field.type_for(field_type), default: default, validators: [], ) define_method("__set_field_#{field_name}") do __fields[field_name.to_sym] ||= self.class.__fields[field_name.to_sym].dup end define_method(field_name) do send("__set_field_#{field_name}") __fields[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| send("__set_field_#{field_name}") __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) __fields[field_name] .validate(validator, message: message, &validation_block) end def __validate! __fields.each do |field_name, unvalidated_field| __fields[field_name].value = unvalidated_field.validate!(self) end end end end