business_flow 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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