module BusinessFlow class Step class Inputs attr_reader :inputs def initialize(inputs) @inputs = inputs end def parameters_from_source(source) return source if inputs.blank? Hash[inputs.map do |input_name, input_value| [ input_name, Inputs.process_input(source, input_value) ] end] end def self.process_input(source, input_value) case input_value when Symbol source.send(input_value) when Proc source.instance_exec(&input_value) else input_value end end end class Result def initialize(result, output_map) @result = result @output_map = output_map @result_errors = begin result.errors rescue NoMethodError nil end end def merge_into(object) merge_errors_into(object.errors) merge_outputs_into(object) end def executed? true end def errors? @result_errors.present? end def self.process_output(object, output, output_setter) case output_setter when Symbol object.send("#{output_setter}=", output) when Proc object.instance_exec(output, &output_setter) end end private def merge_errors_into(errors) return if @result_errors.blank? @result_errors.each do |attribute, message| attribute = "#{@result.class.name}.#{attribute}" (errors[attribute] << message).uniq! end throw :halt_step end def merge_outputs_into(object) @output_map.each do |(output_name, output_setter)| output = @result.public_send(output_name) Result.process_output(object, output, output_setter) end end end # Returned if our conditional check failed. Does nothing. class ConditionFailedResult def executed? false end def errors? false end def merge_into(_object); end end attr_reader :outputs def initialize(callable, opts) @callable = callable @input_object = Inputs.new(opts[:inputs] || {}) @outputs = opts[:outputs] || {} @condition = opts[:condition] || proc { true } end # @klass can be a symbol or class def call(parameter_source) parameters = @input_object.parameters_from_source(parameter_source) if @condition.call(parameter_source, parameters) Result.new(@callable.call(parameter_source, parameters), outputs) else ConditionFailedResult.new end end def inputs @input_object.inputs end def to_s @callable.to_s end end end