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 +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
|