# typed: false # frozen_string_literal: true require 'active_model' require 'clean_architecture/use_cases/errors' require 'clean_architecture/entities/failure_details' require 'dry/struct' require 'dry/monads/try' require 'dry/matcher/result_matcher' module CleanArchitecture module UseCases class Form include ActiveModel::Conversion include Dry::Monads::Try::Mixin extend ActiveModel::Naming attr_reader :errors, :context def initialize(params: {}, context: {}, errors: UseCases::Errors.new(nil)) @params = params.to_h.symbolize_keys @context = context @errors = errors end def persisted? false end # Attempts to create the parameter object returning a Dry::Monads::Result with either # the parameter object or an exception def to_parameter_object @to_parameter_object ||= begin use_case_class.parameters(parameter_object_hash.merge(context: context)) end end def with_errors(new_errors, override_params: nil) self.class.new( params: override_params || @params, context: @context, errors: ErrorsFactory.new(new_errors).manufacture ) end def with_error_message(message, override_params: nil) new_errors = UseCases::Errors.new(nil) new_errors.add(:base, message) with_errors(new_errors, override_params: override_params) end def self.acts_as_form_for(use_case_class) attribute_names = use_case_class.contract.__schema__.rules.keys attribute_names.each do |attribute_name| define_method attribute_name do to_parameter_object[attribute_name] end end define_method :use_case_class do use_case_class end end def self.prepopulate_with(_object, _context) raise NotImplementedError, 'This form does not support prepopulation' end private def attempt_parameter_object_hash_creation errors.clear parameter_object_hash end # This can be overridden for forms where the params don't map perfectly # to the parameter object def parameter_object_hash @params end class ErrorsFactory def initialize(errors) @errors = errors end def manufacture if @errors.is_a?(UseCases::Errors) return @errors elsif @errors.is_a?(Entities::FailureDetails) return errors_from_failure_details elsif @errors.is_a?(String) return errors_from_string end raise ArgumentError, "Unable to handle errors of type: #{@errors.class}" end private def errors_from_failure_details errors = UseCases::Errors.new(nil) errors.add(:base, @errors.message) errors end def errors_from_string errors = UseCases::Errors.new(nil) errors.add(:base, @errors) errors end end end end end