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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/business_flow/base.rb +13 -85
- data/lib/business_flow/callable.rb +54 -0
- data/lib/business_flow/dsl.rb +88 -0
- data/lib/business_flow/step.rb +8 -32
- data/lib/business_flow/version.rb +1 -1
- data/lib/business_flow.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59f3aeae56b561faae00b90ca42cda1eb822124b
|
4
|
+
data.tar.gz: 937c3212f40c0dcfa708e94b846f225f9d46e161
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ae409cc67496b6e54c228c7f93cb93cbf6efe837878c15a5be5dce789036e9bcdbf5e38ded9b178016026a73d3ccc30b9e402f71097353d97c3e649a73b4add
|
7
|
+
data.tar.gz: d31ee7ddf43d7d5ac03f0d1d51e3d64d3893a38de3939e434dfe3d340f7a2449fba0b317258b22b059c2251a750299f038fbebafbfbeedf34b18501a37d3d154
|
data/Gemfile.lock
CHANGED
data/lib/business_flow/base.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
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
|
data/lib/business_flow/step.rb
CHANGED
@@ -1,43 +1,19 @@
|
|
1
1
|
module BusinessFlow
|
2
2
|
class Step
|
3
3
|
attr_reader :klass, :inputs, :outputs
|
4
|
-
def initialize(klass,
|
4
|
+
def initialize(klass, opts, flow_klass)
|
5
5
|
@klass = klass
|
6
|
-
@inputs = inputs
|
7
|
-
@outputs = outputs
|
8
|
-
|
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
|
-
|
14
|
-
|
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
|
data/lib/business_flow.rb
CHANGED
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.
|
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
|