business_flow 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad421ec64fb9e1b5b196c149b715a27c0a2c4f41
4
- data.tar.gz: 8088761788568a2246509d4b89c44ee62e4ce1f2
3
+ metadata.gz: 59f3aeae56b561faae00b90ca42cda1eb822124b
4
+ data.tar.gz: 937c3212f40c0dcfa708e94b846f225f9d46e161
5
5
  SHA512:
6
- metadata.gz: 80f20f73dcb164fedefbd6248e9d498505f684e4ac81c21fd9469d68524dae6c3f1cbad775982f68f198ed154dc566a299c0098ac3d81b03ee21b73e8b3f4e14
7
- data.tar.gz: 16e5ee6e8c62b0b69724b19503f0e989d3d46334df6f058c041ad459ed0b1d43096ccd6d12a112f9445000975bd284ae3c897fea262e24e716b3b4255f21b1f0
6
+ metadata.gz: 6ae409cc67496b6e54c228c7f93cb93cbf6efe837878c15a5be5dce789036e9bcdbf5e38ded9b178016026a73d3ccc30b9e402f71097353d97c3e649a73b4add
7
+ data.tar.gz: d31ee7ddf43d7d5ac03f0d1d51e3d64d3893a38de3939e434dfe3d340f7a2449fba0b317258b22b059c2251a750299f038fbebafbfbeedf34b18501a37d3d154
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- business_flow (0.1.0)
4
+ business_flow (0.1.2)
5
5
  activemodel (>= 3.0)
6
6
 
7
7
  GEM
@@ -10,84 +10,11 @@ module BusinessFlow
10
10
  # ActiveSupport notifiers
11
11
  class Base
12
12
  include ActiveModel::Validations
13
+ include DSL
13
14
 
14
- class << self
15
- alias invariant validates
16
- end
17
-
18
- class_attribute :step_queue, :requirements
19
15
  attr_reader :parameter_object
20
16
  private :parameter_object
21
17
 
22
- def self.add_requirement(fields)
23
- @requirements ||= []
24
- @requirements.concat(fields)
25
- fields.each do |field|
26
- validates_with NotNilValidator, attributes: [field]
27
- end
28
- end
29
-
30
- def self.needs(*fields)
31
- add_requirement(fields)
32
- wants(*fields)
33
- end
34
-
35
- def self.wants(*fields)
36
- fields.each { |field| add_field(field) }
37
- end
38
-
39
- def self.provides(*fields)
40
- attr_reader(*fields)
41
- end
42
-
43
- def self.expects(field, options = {})
44
- validates field, options.merge(on: field)
45
- end
46
-
47
- def self.add_field(field)
48
- define_method field do
49
- if parameter_object.is_a?(Hash) && parameter_object.key?(field)
50
- parameter_object[field]
51
- else
52
- parameter_object.public_send(field)
53
- end
54
- end
55
- end
56
-
57
- def self.steps(*step_queue)
58
- self.step_queue = step_queue.map do |step|
59
- if !step.is_a?(Step)
60
- Step.new(step, {}, {}, self)
61
- else
62
- step
63
- end
64
- end
65
- end
66
-
67
- def self.step(klass, inputs = {}, outputs = {})
68
- create_fields_for_step_outputs(outputs)
69
- Step.new(klass, inputs, outputs, self)
70
- end
71
-
72
- def self.create_fields_for_step_outputs(outputs)
73
- outputs.values.select { |field| field.is_a?(Symbol) }.map do |field|
74
- attr_reader field
75
- create_setter(field)
76
- end
77
- end
78
-
79
- def self.create_setter(field)
80
- define_method "#{field}=" do |new_value|
81
- instance_variable_set("@#{field}", new_value)
82
- valid?(field)
83
- new_value
84
- end
85
- end
86
-
87
- def self.call(parameter_object)
88
- new(parameter_object).tap(&:call)
89
- end
90
-
91
18
  def initialize(parameter_object)
92
19
  @parameter_object = parameter_object
93
20
  end
@@ -97,18 +24,13 @@ module BusinessFlow
97
24
  process_steps
98
25
  end
99
26
 
100
- def merge_errors_into(other_errors)
101
- errors.each do |attribute, message|
102
- attribute = "#{self.class.name.underscore}.#{attribute}"
103
- (other_errors[attribute] << message).uniq!
104
- end
105
- end
106
-
107
27
  private
108
28
 
109
29
  def process_steps
110
30
  steps.each do |step_name|
111
- process_step(step_name)
31
+ catch(:halt_step) do
32
+ process_step(step_name)
33
+ end
112
34
  break if errors.any?
113
35
  end
114
36
  end
@@ -131,12 +53,18 @@ module BusinessFlow
131
53
  end
132
54
 
133
55
  def marshall_outputs(result, output_object)
134
- result.merge_errors_into(errors)
135
- return if errors.any? || output_object.blank?
56
+ merge_other_errors(result.errors, result.class.name.underscore)
136
57
  output_object.each do |(output_name, output_setter)|
58
+ break if errors.any?
137
59
  output = result.public_send(output_name)
138
60
  process_output(output, output_setter)
139
- break if errors.any?
61
+ end
62
+ end
63
+
64
+ def merge_other_errors(other_errors, base_name)
65
+ other_errors.each do |attribute, message|
66
+ attribute = "#{base_name}.#{attribute}"
67
+ (errors[attribute] << message).uniq!
140
68
  end
141
69
  end
142
70
 
@@ -0,0 +1,54 @@
1
+ module BusinessFlow
2
+ # Isolate the logic around invoking a 'callable' -- a symbol representing a
3
+ # method, a symbol representing another class which implements .call, or a
4
+ # proc/lambda
5
+ class Callable
6
+ def initialize(callable, metaclass)
7
+ @metaclass = metaclass
8
+ @callable = callable
9
+ check_callable
10
+ end
11
+
12
+ def call(instance, inputs)
13
+ cached_proc.call(instance, inputs)
14
+ end
15
+
16
+ private
17
+
18
+ # :reek:ManualDispatch Look reek I don't know what you want me to do.
19
+ def check_callable
20
+ if @callable.is_a?(Proc)
21
+ @cached_proc = proc do |instance, inputs|
22
+ instance.instance_exec(inputs, &@callable)
23
+ end
24
+ elsif @callable.respond_to?(:call)
25
+ @cached_proc = proc { |_, inputs| @callable.call(inputs) }
26
+ elsif !@callable.is_a?(Symbol)
27
+ raise ArgumentError, 'callable must be a symbol or respond to #call'
28
+ end
29
+ end
30
+
31
+ def cached_proc
32
+ @cached_proc ||=
33
+ if @metaclass.method_defined?(@callable) ||
34
+ @metaclass.private_method_defined?(@callable)
35
+ proc { |instance, _| instance.send(@callable) && nil }
36
+ else
37
+ @callable = lookup_callable ||
38
+ raise(NameError, "undefined constant #{@klass}")
39
+ proc { |_, inputs| @callable.call(inputs) }
40
+ end
41
+ end
42
+
43
+ def lookup_callable
44
+ constant_name = @callable.to_s.camelcase
45
+ @metaclass.parents.each do |parent|
46
+ begin
47
+ break parent.const_get(constant_name)
48
+ rescue NameError
49
+ next
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,88 @@
1
+ module BusinessFlow
2
+ module DSL
3
+ def self.included(klass)
4
+ klass.include(ActiveModel::Validations)
5
+ klass.extend(ClassMethods)
6
+ klass.instance_eval do
7
+ class << self
8
+ alias invariant validates
9
+ end
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def step_queue
15
+ @step_queue ||= []
16
+ end
17
+
18
+ def add_requirement(fields)
19
+ @requirements ||= []
20
+ @requirements.concat(fields)
21
+ fields.each do |field|
22
+ validates_with NotNilValidator, attributes: [field]
23
+ end
24
+ end
25
+
26
+ def needs(*fields)
27
+ add_requirement(fields)
28
+ wants(*fields)
29
+ end
30
+
31
+ def wants(*fields)
32
+ fields.each { |field| add_parameter_field(field) }
33
+ end
34
+
35
+ def provides(*fields)
36
+ attr_reader(*fields)
37
+ attr_writer(*fields)
38
+ fields.each { |field| private("#{field}=") }
39
+ end
40
+
41
+ def expects(field, options = {})
42
+ attr_reader field
43
+ validates field, options.merge(on: field)
44
+ setter_name = "#{field}=".to_sym
45
+ unless method_defined?(setter_name) ||
46
+ private_method_defined?(setter_name)
47
+ create_setter(field)
48
+ end
49
+ end
50
+
51
+ def add_parameter_field(field)
52
+ define_method field do
53
+ if parameter_object.is_a?(Hash) && parameter_object.key?(field)
54
+ parameter_object[field]
55
+ else
56
+ parameter_object.public_send(field)
57
+ end
58
+ end
59
+ end
60
+
61
+ def step(klass, opts = {})
62
+ create_fields_for_step_outputs(opts.fetch(:outputs, {}))
63
+ step_queue << Step.new(klass, opts, self)
64
+ end
65
+
66
+ def create_fields_for_step_outputs(outputs)
67
+ outputs.values.select { |field| field.is_a?(Symbol) }.map do |field|
68
+ attr_reader field
69
+ create_setter(field)
70
+ end
71
+ end
72
+
73
+ def create_setter(field)
74
+ setter_name = "#{field}=".to_sym
75
+ define_method setter_name do |new_value|
76
+ instance_variable_set("@#{field}".to_sym, new_value)
77
+ throw :halt_step unless valid?(field)
78
+ new_value
79
+ end
80
+ private(setter_name)
81
+ end
82
+
83
+ def call(parameter_object)
84
+ new(parameter_object).tap(&:call)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,43 +1,19 @@
1
1
  module BusinessFlow
2
2
  class Step
3
3
  attr_reader :klass, :inputs, :outputs
4
- def initialize(klass, inputs, outputs, flow_klass)
4
+ def initialize(klass, opts, flow_klass)
5
5
  @klass = klass
6
- @inputs = inputs
7
- @outputs = outputs
8
- @flow_klass = flow_klass
6
+ @inputs = opts.fetch(:inputs, {})
7
+ @outputs = opts.fetch(:outputs, {})
8
+ if_condition = opts[:if]
9
+ @condition = Callable.new(if_condition, flow_klass) if if_condition
10
+ @callable = Callable.new(@klass, flow_klass)
9
11
  end
10
12
 
11
13
  # @klass can be a symbol or class
12
14
  def dispatch(flow, inputs)
13
- callable.call(flow, inputs)
14
- end
15
-
16
- private
17
-
18
- def callable
19
- @callable ||=
20
- if @klass.is_a?(Symbol) &&
21
- @flow_klass.method_defined?(@klass) ||
22
- @flow_klass.private_method_defined?(@klass)
23
- proc { |flow, _| flow.send(@klass) && nil }
24
- else
25
- @klass = lookup_klass ||
26
- raise(NameError, "undefined constant #{@klass}")
27
- proc { |_, inputs| @klass.call(inputs) }
28
- end
29
- end
30
-
31
- def lookup_klass
32
- return @klass unless @klass.is_a?(Symbol)
33
- constant_name = @klass.to_s.camelcase
34
- @flow_klass.parents.each do |parent|
35
- begin
36
- break parent.const_get(constant_name)
37
- rescue NameError
38
- next
39
- end
40
- end
15
+ return if @condition && !@condition.call(flow, inputs)
16
+ @callable.call(flow, inputs)
41
17
  end
42
18
  end
43
19
  end
@@ -1,3 +1,3 @@
1
1
  module BusinessFlow
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/business_flow.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'active_model'
2
2
  require 'business_flow/version'
3
3
  require 'business_flow/not_nil_validator'
4
+ require 'business_flow/callable'
4
5
  require 'business_flow/step'
6
+ require 'business_flow/dsl'
5
7
  require 'business_flow/base'
6
8
 
7
9
  module BusienssFlow
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: business_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Scarborough
@@ -114,6 +114,8 @@ files:
114
114
  - business_flow.gemspec
115
115
  - lib/business_flow.rb
116
116
  - lib/business_flow/base.rb
117
+ - lib/business_flow/callable.rb
118
+ - lib/business_flow/dsl.rb
117
119
  - lib/business_flow/not_nil_validator.rb
118
120
  - lib/business_flow/step.rb
119
121
  - lib/business_flow/version.rb