module BusinessFlow module DSL def self.included(klass) klass.include(ActiveModel::Validations) klass.extend(ClassMethods) klass.instance_eval do class << self alias invariant validates end end end module ClassMethods def step_queue @step_queue ||= [] end def add_requirement(fields) @requirements ||= [] @requirements.concat(fields) fields.each do |field| validates_with NotNilValidator, attributes: [field] end end def needs(*fields) add_requirement(fields) wants(*fields) end def wants(*fields) fields.each { |field| add_parameter_field(field) } end def provides(*fields) attr_reader(*fields) attr_writer(*fields) fields.each { |field| private("#{field}=") } end def expects(field, options = {}) attr_reader field validates field, options.merge(on: field) setter_name = "#{field}=".to_sym unless method_defined?(setter_name) || private_method_defined?(setter_name) create_setter(field) end end def add_parameter_field(field) define_method field do if parameter_object.is_a?(Hash) && parameter_object.key?(field) parameter_object[field] else parameter_object.public_send(field) end end end def step(klass, opts = {}) create_fields_for_step_outputs(opts.fetch(:outputs, {})) step_queue << Step.new(klass, opts, self) end def create_fields_for_step_outputs(outputs) outputs.values.select { |field| field.is_a?(Symbol) }.map do |field| attr_reader field create_setter(field) end end def create_setter(field) setter_name = "#{field}=".to_sym define_method setter_name do |new_value| instance_variable_set("@#{field}".to_sym, new_value) throw :halt_step unless valid?(field) new_value end private(setter_name) end def call(parameter_object) new(parameter_object).tap(&:call) end end end end