business_flow 0.19.3 → 0.19.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.reek.yml +13 -0
- data/.rubocop.yml +84 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +72 -36
- data/Rakefile +2 -0
- data/bin/console +2 -0
- data/business_flow.gemspec +4 -8
- data/lib/business_flow/cacheable.rb +7 -4
- data/lib/business_flow/callable.rb +3 -5
- data/lib/business_flow/cluster_lock.rb +37 -28
- data/lib/business_flow/compat.rb +9 -6
- data/lib/business_flow/default_step_executor.rb +1 -0
- data/lib/business_flow/dsl.rb +44 -30
- data/lib/business_flow/instrument.rb +2 -2
- data/lib/business_flow/instrumented_executor.rb +2 -1
- data/lib/business_flow/retryable.rb +1 -1
- data/lib/business_flow/step.rb +15 -16
- data/lib/business_flow/version.rb +1 -1
- data/lib/business_flow.rb +11 -0
- metadata +9 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82c431371d59045db43a3e6a53b1f5be3169396eb684cd3dd57360c13e66de93
|
4
|
+
data.tar.gz: 7af7df17705aba80a4c27099a72a122413029bf95f73490d87de2ae66336dd4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27e674b0357b3312991ad8d65c91b95e0fed8481259512bdee9e3e2a262e9841af6a1d2b30de250781de36e4aa7544a5d17482ee75013067c9f59c837603958d
|
7
|
+
data.tar.gz: 8c3fe93115e9ac88c71812c1392e9a88702a410ce904f12b2982640a115066a93b1b676fc671be99ef9675073f5e90454d231409e2c13c67ded91f595aa588ae
|
data/.reek.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Auto generated by Reeks --todo flag
|
2
|
+
---
|
3
|
+
detectors:
|
4
|
+
FeatureEnvy:
|
5
|
+
exclude:
|
6
|
+
- BusinessFlow::ClusterLock::ClassMethods::LockFailure#add_to
|
7
|
+
LongParameterList:
|
8
|
+
exclude:
|
9
|
+
- BusinessFlow#self.add_error
|
10
|
+
UncommunicativeVariableName:
|
11
|
+
# N.B. If we want to enable this, we need to tweak the RuboCop config. In particular, RuboCop
|
12
|
+
# prefers `e` for exceptions.
|
13
|
+
enabled: false
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 2.7
|
6
|
+
NewCops: enable
|
7
|
+
Exclude:
|
8
|
+
- coverage/**/*
|
9
|
+
|
10
|
+
# Excluding specs here because of RSpec's tendency to look at the line-number info on a backtrace to
|
11
|
+
# decide what to show you when a matcher fails. I.E. a very long line may be needed to ensure you
|
12
|
+
# get good diagnostic info on a test failure.
|
13
|
+
Layout/LineLength:
|
14
|
+
Max: 100
|
15
|
+
Exclude:
|
16
|
+
- spec/**/*
|
17
|
+
|
18
|
+
RSpec/MultipleMemoizedHelpers:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
RSpec/IndexedLet:
|
22
|
+
# We have specs that are describing process steps, so... numbers are kinda the best way to describe that.
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
RSpec/NestedGroups:
|
26
|
+
Max: 4
|
27
|
+
|
28
|
+
RSpec/LeakyConstantDeclaration:
|
29
|
+
# This effectively duplicates Lint/ConstantDefinitionInBlock.
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Lint/ConstantDefinitionInBlock:
|
33
|
+
Exclude:
|
34
|
+
- spec/**/*
|
35
|
+
|
36
|
+
Naming/MemoizedInstanceVariableName:
|
37
|
+
# This conflicts with Reek.
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
# Explicit is bettr than implicit.
|
41
|
+
Style/EmptyElse:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Style/RescueModifier:
|
45
|
+
Exclude:
|
46
|
+
- spec/**/*
|
47
|
+
|
48
|
+
Style/DocumentDynamicEvalDefinition:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
# Explicit is bettr than implicit.
|
52
|
+
FactoryBot/SyntaxMethods:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
# For `match(hash_including(...))`, in specs, this rule would make things pretty unreadable.
|
56
|
+
Layout/ClosingParenthesisIndentation:
|
57
|
+
Exclude:
|
58
|
+
- spec/**/*
|
59
|
+
|
60
|
+
# This makes things pretty unreadable in a lot of cases, including but not limited to
|
61
|
+
# `match(hash_including(...))` in specs.
|
62
|
+
Layout/FirstArgumentIndentation:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Bundler/OrderedGems:
|
66
|
+
Exclude:
|
67
|
+
- 'Gemfile'
|
68
|
+
|
69
|
+
Lint/EmptyBlock:
|
70
|
+
Enabled: false
|
71
|
+
|
72
|
+
# OpenStruct is unwise for actual application code (esp. in light of potentially using yjit in the
|
73
|
+
# future), but the flexibility is handy for specs.
|
74
|
+
Style/OpenStructUse:
|
75
|
+
Exclude:
|
76
|
+
- spec/**/*
|
77
|
+
|
78
|
+
# Disabling this because the "preferred" way requires allocating a Range which is stupid and wrong.
|
79
|
+
Style/RandomWithOffset:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
# What, exactly, does the Ruby community have against the Truthiness Operator?!
|
83
|
+
Style/DoubleNegation:
|
84
|
+
Enabled: false
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
business_flow
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.7.4
|
data/Gemfile
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in business_flow.gemspec
|
6
8
|
gemspec
|
9
|
+
|
10
|
+
gem 'pry', '~> 0.14.2'
|
11
|
+
gem 'rake', '~> 13.0'
|
12
|
+
gem 'reek', '~> 6.1'
|
13
|
+
gem 'retryable', '~> 3.0.4'
|
14
|
+
gem 'rspec', '~> 3.0'
|
15
|
+
gem 'rubocop', '~> 1.59.0'
|
16
|
+
gem 'rubocop-rspec', '~> 2.29.1'
|
17
|
+
gem 'simplecov', '~> 0.22.0'
|
18
|
+
gem 'timecop', '~> 0.9.1'
|
data/Gemfile.lock
CHANGED
@@ -1,84 +1,120 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
business_flow (0.19.
|
4
|
+
business_flow (0.19.5)
|
5
5
|
activemodel (>= 4.2, < 8)
|
6
6
|
activesupport (>= 4.2, < 8)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (7.
|
12
|
-
activesupport (= 7.
|
13
|
-
activesupport (7.
|
11
|
+
activemodel (7.1.3.2)
|
12
|
+
activesupport (= 7.1.3.2)
|
13
|
+
activesupport (7.1.3.2)
|
14
|
+
base64
|
15
|
+
bigdecimal
|
14
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
connection_pool (>= 2.2.5)
|
18
|
+
drb
|
15
19
|
i18n (>= 1.6, < 2)
|
16
20
|
minitest (>= 5.1)
|
21
|
+
mutex_m
|
17
22
|
tzinfo (~> 2.0)
|
18
23
|
ast (2.4.2)
|
19
|
-
|
20
|
-
|
24
|
+
base64 (0.2.0)
|
25
|
+
bigdecimal (3.1.7)
|
26
|
+
coderay (1.1.3)
|
27
|
+
concurrent-ruby (1.2.3)
|
28
|
+
connection_pool (2.4.1)
|
29
|
+
diff-lcs (1.5.1)
|
21
30
|
docile (1.4.0)
|
22
|
-
|
31
|
+
drb (2.2.1)
|
32
|
+
i18n (1.14.4)
|
23
33
|
concurrent-ruby (~> 1.0)
|
24
|
-
|
34
|
+
json (2.7.2)
|
25
35
|
kwalify (0.7.2)
|
26
|
-
|
27
|
-
|
28
|
-
|
36
|
+
language_server-protocol (3.17.0.3)
|
37
|
+
method_source (1.1.0)
|
38
|
+
minitest (5.22.3)
|
39
|
+
mutex_m (0.2.0)
|
40
|
+
parallel (1.24.0)
|
41
|
+
parser (3.2.2.4)
|
29
42
|
ast (~> 2.4.1)
|
43
|
+
racc
|
44
|
+
pry (0.14.2)
|
45
|
+
coderay (~> 1.1)
|
46
|
+
method_source (~> 1.0)
|
47
|
+
racc (1.7.3)
|
30
48
|
rainbow (3.1.1)
|
31
|
-
rake (
|
49
|
+
rake (13.2.1)
|
32
50
|
reek (6.1.4)
|
33
51
|
kwalify (~> 0.7.0)
|
34
52
|
parser (~> 3.2.0)
|
35
53
|
rainbow (>= 2.0, < 4.0)
|
54
|
+
regexp_parser (2.9.0)
|
36
55
|
retryable (3.0.5)
|
37
|
-
|
38
|
-
|
39
|
-
rspec-
|
40
|
-
rspec-
|
41
|
-
|
42
|
-
|
43
|
-
|
56
|
+
rexml (3.2.6)
|
57
|
+
rspec (3.13.0)
|
58
|
+
rspec-core (~> 3.13.0)
|
59
|
+
rspec-expectations (~> 3.13.0)
|
60
|
+
rspec-mocks (~> 3.13.0)
|
61
|
+
rspec-core (3.13.0)
|
62
|
+
rspec-support (~> 3.13.0)
|
63
|
+
rspec-expectations (3.13.0)
|
44
64
|
diff-lcs (>= 1.2.0, < 2.0)
|
45
|
-
rspec-support (~> 3.
|
46
|
-
rspec-mocks (3.
|
65
|
+
rspec-support (~> 3.13.0)
|
66
|
+
rspec-mocks (3.13.0)
|
47
67
|
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.
|
49
|
-
rspec-support (3.
|
50
|
-
rubocop (
|
51
|
-
|
68
|
+
rspec-support (~> 3.13.0)
|
69
|
+
rspec-support (3.13.1)
|
70
|
+
rubocop (1.59.0)
|
71
|
+
json (~> 2.3)
|
72
|
+
language_server-protocol (>= 3.17.0)
|
52
73
|
parallel (~> 1.10)
|
53
|
-
parser (>= 2.
|
74
|
+
parser (>= 3.2.2.4)
|
54
75
|
rainbow (>= 2.2.2, < 4.0)
|
76
|
+
regexp_parser (>= 1.8, < 3.0)
|
77
|
+
rexml (>= 3.2.5, < 4.0)
|
78
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
55
79
|
ruby-progressbar (~> 1.7)
|
56
|
-
unicode-display_width (>=
|
57
|
-
rubocop-
|
58
|
-
|
59
|
-
|
80
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
81
|
+
rubocop-ast (1.30.0)
|
82
|
+
parser (>= 3.2.1.0)
|
83
|
+
rubocop-capybara (2.20.0)
|
84
|
+
rubocop (~> 1.41)
|
85
|
+
rubocop-factory_bot (2.25.1)
|
86
|
+
rubocop (~> 1.41)
|
87
|
+
rubocop-rspec (2.29.2)
|
88
|
+
rubocop (~> 1.40)
|
89
|
+
rubocop-capybara (~> 2.17)
|
90
|
+
rubocop-factory_bot (~> 2.22)
|
91
|
+
rubocop-rspec_rails (~> 2.28)
|
92
|
+
rubocop-rspec_rails (2.28.3)
|
93
|
+
rubocop (~> 1.40)
|
94
|
+
ruby-progressbar (1.13.0)
|
60
95
|
simplecov (0.22.0)
|
61
96
|
docile (~> 1.1)
|
62
97
|
simplecov-html (~> 0.11)
|
63
98
|
simplecov_json_formatter (~> 0.1)
|
64
99
|
simplecov-html (0.12.3)
|
65
100
|
simplecov_json_formatter (0.1.4)
|
66
|
-
timecop (0.9.
|
67
|
-
tzinfo (2.0.
|
101
|
+
timecop (0.9.8)
|
102
|
+
tzinfo (2.0.6)
|
68
103
|
concurrent-ruby (~> 1.0)
|
69
|
-
unicode-display_width (
|
104
|
+
unicode-display_width (2.5.0)
|
70
105
|
|
71
106
|
PLATFORMS
|
72
107
|
ruby
|
73
108
|
|
74
109
|
DEPENDENCIES
|
75
110
|
business_flow!
|
76
|
-
|
111
|
+
pry (~> 0.14.2)
|
112
|
+
rake (~> 13.0)
|
77
113
|
reek (~> 6.1)
|
78
114
|
retryable (~> 3.0.4)
|
79
115
|
rspec (~> 3.0)
|
80
|
-
rubocop (~> 0
|
81
|
-
rubocop-rspec (~>
|
116
|
+
rubocop (~> 1.59.0)
|
117
|
+
rubocop-rspec (~> 2.29.1)
|
82
118
|
simplecov (~> 0.22.0)
|
83
119
|
timecop (~> 0.9.1)
|
84
120
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/business_flow.gemspec
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -23,12 +24,7 @@ Gem::Specification.new do |spec|
|
|
23
24
|
spec.add_dependency 'activemodel', '>= 4.2', '< 8'
|
24
25
|
spec.add_dependency 'activesupport', '>= 4.2', '< 8'
|
25
26
|
|
26
|
-
spec.
|
27
|
-
|
28
|
-
spec.
|
29
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
-
spec.add_development_dependency 'rubocop', '~> 0.53'
|
31
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 1.24.0'
|
32
|
-
spec.add_development_dependency 'simplecov', '~> 0.22.0'
|
33
|
-
spec.add_development_dependency 'timecop', '~> 0.9.1'
|
27
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
28
|
+
|
29
|
+
spec.required_ruby_version = '>= 2.7.0'
|
34
30
|
end
|
@@ -68,13 +68,14 @@ module BusinessFlow
|
|
68
68
|
|
69
69
|
def clear_cache(parameter_object = {})
|
70
70
|
clear_cache!(parameter_object)
|
71
|
-
rescue FlowFailedException =>
|
72
|
-
|
71
|
+
rescue FlowFailedException => e
|
72
|
+
e.flow
|
73
73
|
end
|
74
74
|
|
75
75
|
def clear_cache!(parameter_object = {})
|
76
76
|
flow = build(parameter_object)
|
77
77
|
raise FlowFailedException, result_from(flow) if flow.errors?
|
78
|
+
|
78
79
|
ClassMethods.clear_cache(flow)
|
79
80
|
end
|
80
81
|
|
@@ -91,8 +92,8 @@ module BusinessFlow
|
|
91
92
|
with_cache(flow) do
|
92
93
|
super(flow)._business_flow_cacheable_finalize(flow.cache_key)
|
93
94
|
end
|
94
|
-
rescue FlowFailedException =>
|
95
|
-
|
95
|
+
rescue FlowFailedException => e
|
96
|
+
e.flow
|
96
97
|
end
|
97
98
|
|
98
99
|
def with_cache(flow, &blk)
|
@@ -117,11 +118,13 @@ module BusinessFlow
|
|
117
118
|
RESULT_FINALIZE = proc do |cache_key|
|
118
119
|
@cache_key = cache_key
|
119
120
|
raise FlowFailedException, self if errors?
|
121
|
+
|
120
122
|
self
|
121
123
|
end
|
122
124
|
|
123
125
|
def add_cache_key_to_result_class
|
124
126
|
return if @cache_key_added
|
127
|
+
|
125
128
|
result_class = const_get(:Result)
|
126
129
|
DSL::PublicField.new(:cache_key).add_to(result_class)
|
127
130
|
result_class.send(:define_method, :_business_flow_cacheable_finalize,
|
@@ -98,11 +98,9 @@ module BusinessFlow
|
|
98
98
|
def lookup_callable(first_instance)
|
99
99
|
constant_name = @callable.to_s.camelcase
|
100
100
|
first_instance.class.module_parents.each do |parent|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
next
|
105
|
-
end
|
101
|
+
return parent.const_get(constant_name)
|
102
|
+
rescue NameError
|
103
|
+
next
|
106
104
|
end
|
107
105
|
nil
|
108
106
|
end
|
@@ -20,23 +20,24 @@ module BusinessFlow
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.default_servers=(servers)
|
23
|
-
if servers.is_a?(String)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
@default_servers = if servers.is_a?(String)
|
24
|
+
proc { servers }
|
25
|
+
elsif servers
|
26
|
+
Callable.new(servers)
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.default_servers
|
33
|
-
@default_servers ||= proc {
|
33
|
+
@default_servers ||= proc {}
|
34
34
|
end
|
35
35
|
|
36
36
|
def assert_cluster_lock!
|
37
|
-
@_business_flow_cluster_lock.assert!
|
38
|
-
rescue ZK::Exceptions::ZKError =>
|
39
|
-
|
37
|
+
@_business_flow_cluster_lock.assert! unless BusinessFlow::ClusterLock.disabled?
|
38
|
+
rescue ZK::Exceptions::ZKError => e
|
39
|
+
BusinessFlow.add_error(errors, :cluster_lock, :assert_failed, e.message)
|
40
|
+
|
40
41
|
raise
|
41
42
|
end
|
42
43
|
|
@@ -49,10 +50,11 @@ module BusinessFlow
|
|
49
50
|
end
|
50
51
|
|
51
52
|
# DSL Methods
|
52
|
-
module ClassMethods
|
53
|
+
module ClassMethods # rubocop:disable Metrics/ModuleLength
|
53
54
|
# Error raised when there is an internal issue with acquiring a lock.
|
54
55
|
class LockFailure < StandardError
|
55
56
|
attr_reader :error_type
|
57
|
+
|
56
58
|
def initialize(error_type, message)
|
57
59
|
@error_type = error_type
|
58
60
|
super(message)
|
@@ -60,7 +62,9 @@ module BusinessFlow
|
|
60
62
|
|
61
63
|
def add_to(flow)
|
62
64
|
errors = flow.errors
|
63
|
-
|
65
|
+
unless errors.key?(:cluster_lock)
|
66
|
+
BusinessFlow.add_error(errors, :cluster_lock, error_type, message)
|
67
|
+
end
|
64
68
|
flow
|
65
69
|
end
|
66
70
|
end
|
@@ -114,8 +118,8 @@ module BusinessFlow
|
|
114
118
|
ClassMethods.with_lock(flow, lock_info) do
|
115
119
|
super(flow)._business_flow_cluster_lock_finalize(lock_info)
|
116
120
|
end
|
117
|
-
rescue LockFailure =>
|
118
|
-
|
121
|
+
rescue LockFailure => e
|
122
|
+
result_from(e.add_to(flow))._business_flow_cluster_lock_finalize(lock_info)
|
119
123
|
end
|
120
124
|
|
121
125
|
RESULT_FINALIZE = proc do |cluster_lock_info|
|
@@ -125,6 +129,7 @@ module BusinessFlow
|
|
125
129
|
|
126
130
|
def add_cluster_luck_info_to_result_class
|
127
131
|
return if @cluster_lock_info_added
|
132
|
+
|
128
133
|
result_class = const_get(:Result)
|
129
134
|
DSL::PublicField.new(:cluster_lock_info).add_to(result_class)
|
130
135
|
result_class.send(:define_method, :_business_flow_cluster_lock_finalize,
|
@@ -134,19 +139,23 @@ module BusinessFlow
|
|
134
139
|
|
135
140
|
# :reek:NilCheck
|
136
141
|
def self.zookeeper_server_list(flow)
|
137
|
-
servers =
|
138
|
-
|
142
|
+
servers =
|
143
|
+
catch(:halt_step) { flow.class.with_zookeeper_servers.call(flow)&.merge_into(flow)&.to_s }
|
144
|
+
if servers.nil? || servers.empty?
|
139
145
|
raise LockFailure.new(:no_servers, 'no zookeeper servers provided')
|
140
146
|
end
|
147
|
+
|
141
148
|
servers
|
142
149
|
end
|
143
150
|
|
144
151
|
# :reek:NilCheck
|
145
152
|
def self.lock_name(flow)
|
146
|
-
lock_name =
|
147
|
-
|
153
|
+
lock_name =
|
154
|
+
catch(:halt_step) { flow.class.with_cluster_lock.call(flow)&.merge_into(flow)&.to_s }
|
155
|
+
if lock_name.nil? || lock_name.empty?
|
148
156
|
raise LockFailure.new(:no_lock_name, 'no lock name provided')
|
149
157
|
end
|
158
|
+
|
150
159
|
lock_name
|
151
160
|
end
|
152
161
|
|
@@ -169,7 +178,7 @@ module BusinessFlow
|
|
169
178
|
def self.inner_acquire_lock(zk_connection, lock, payload)
|
170
179
|
lock_held = lock.lock(wait: false)
|
171
180
|
payload[:lock_acquired] = lock_held if payload
|
172
|
-
|
181
|
+
unless lock_held
|
173
182
|
zk_connection.close!
|
174
183
|
raise LockFailure.new(:lock_unavailable, 'the lock was not available')
|
175
184
|
end
|
@@ -178,11 +187,11 @@ module BusinessFlow
|
|
178
187
|
|
179
188
|
def self.cleanup(lock, zk_connection)
|
180
189
|
begin
|
181
|
-
lock
|
190
|
+
lock&.unlock
|
182
191
|
rescue ZK::Exceptions::OperationTimeOut
|
183
192
|
# Just let the connection close handle this.
|
184
193
|
end
|
185
|
-
zk_connection
|
194
|
+
zk_connection&.close!
|
186
195
|
end
|
187
196
|
|
188
197
|
# :reek:ControlParameter
|
@@ -199,16 +208,16 @@ module BusinessFlow
|
|
199
208
|
end
|
200
209
|
end
|
201
210
|
|
202
|
-
def self.with_lock(flow, lock_info, &
|
203
|
-
|
204
|
-
|
211
|
+
def self.with_lock(flow, lock_info, &_blk)
|
212
|
+
unless BusinessFlow::ClusterLock.disabled?
|
213
|
+
zk_connection, lock =
|
205
214
|
instrumented_acquire_lock(flow, lock_info)
|
206
|
-
|
215
|
+
end
|
207
216
|
yield lock_info
|
208
|
-
rescue ZK::Exceptions::LockAssertionFailedError, ZK::Exceptions::OperationTimeOut =>
|
217
|
+
rescue ZK::Exceptions::LockAssertionFailedError, ZK::Exceptions::OperationTimeOut => e
|
209
218
|
# This would occur if we asserted a cluster lock while executing the flow.
|
210
219
|
# This will have set an error on the flow, so we can carry on.
|
211
|
-
raise LockFailure.new(exception_to_error_type(
|
220
|
+
raise LockFailure.new(exception_to_error_type(e), e.message)
|
212
221
|
ensure
|
213
222
|
cleanup(lock, zk_connection)
|
214
223
|
end
|
data/lib/business_flow/compat.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module BusinessFlow
|
4
4
|
# Provides compatibility for ActiveSupport/Model 4.x through 7.x
|
5
5
|
module Compat
|
6
|
-
|
6
|
+
unless Module.instance_methods.include?(:module_parents)
|
7
7
|
# ActiveSupport 5 removed #parents in favor of #module_parents.
|
8
8
|
class ::Module
|
9
9
|
def module_parents
|
@@ -12,13 +12,16 @@ module BusinessFlow
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
unless ActiveModel::Errors.instance_methods.include?(:merge!)
|
16
16
|
# ActiveModel 5 added details (which we do not use here) and #merge!
|
17
17
|
# :reek:MissingSafeMethod Look it's the API.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
module ::ActiveModel
|
19
|
+
# Amend the errors class with a convenience method.
|
20
|
+
class Errors
|
21
|
+
def merge!(other)
|
22
|
+
other.each do |attribute, message|
|
23
|
+
self[attribute] << message
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
data/lib/business_flow/dsl.rb
CHANGED
@@ -5,7 +5,7 @@ module BusinessFlow
|
|
5
5
|
# ClassMethods.
|
6
6
|
module DSL
|
7
7
|
# Contains the DSL for BusinessFlow
|
8
|
-
module ClassMethods
|
8
|
+
module ClassMethods # rubocop:disable Metrics/ModuleLength
|
9
9
|
# Holds metadata about inputs to a flow
|
10
10
|
class Inputs
|
11
11
|
attr_reader :all, :needs, :optionals
|
@@ -56,17 +56,18 @@ module BusinessFlow
|
|
56
56
|
# without having to check for whether or not the wants can be executed.
|
57
57
|
#
|
58
58
|
# :reek:NilCheck This is one of the places where we eliminate nil.
|
59
|
-
|
59
|
+
# :reek:LongParameterList Deal with it
|
60
|
+
def lookup(field, by:, with:, inputs: nil, output: nil)
|
60
61
|
by = Array.wrap(by)
|
61
62
|
optional(*by)
|
62
63
|
wants field, with, unless: -> { by.any? { |input| send(input).nil? } },
|
63
|
-
default_output: field
|
64
|
+
default_output: field, inputs: inputs, output: output
|
64
65
|
end
|
65
66
|
|
66
67
|
# Allows a field to be retrieved from the initialiaztion parameters
|
67
68
|
def wants(field, default = nil, opts = {}, &blk)
|
68
|
-
internal_name = "wants_#{field}"
|
69
|
-
default = proc {
|
69
|
+
internal_name = :"wants_#{field}"
|
70
|
+
default = proc {} unless default || block_given?
|
70
71
|
uses(internal_name, default, opts, &blk)
|
71
72
|
inputs.add_wants(ParameterField.new(field, internal_name))
|
72
73
|
end
|
@@ -105,6 +106,7 @@ module BusinessFlow
|
|
105
106
|
def call(parameter_object = {})
|
106
107
|
flow = build(parameter_object)
|
107
108
|
return result_from(flow) if flow.errors?
|
109
|
+
|
108
110
|
execute(flow)
|
109
111
|
end
|
110
112
|
|
@@ -127,6 +129,7 @@ module BusinessFlow
|
|
127
129
|
def call!(*args)
|
128
130
|
flow = call(*args)
|
129
131
|
raise FlowFailedException, flow if flow.errors?
|
132
|
+
|
130
133
|
flow
|
131
134
|
end
|
132
135
|
|
@@ -136,9 +139,9 @@ module BusinessFlow
|
|
136
139
|
|
137
140
|
def step_executor(executor_class = nil)
|
138
141
|
if executor_class
|
139
|
-
@
|
142
|
+
@step_executor = executor_class
|
140
143
|
else
|
141
|
-
@
|
144
|
+
@step_executor ||= ::BusinessFlow::DefaultStepExecutor
|
142
145
|
end
|
143
146
|
end
|
144
147
|
|
@@ -158,22 +161,32 @@ module BusinessFlow
|
|
158
161
|
|
159
162
|
def finalize_result_provider
|
160
163
|
return if @finalized_result_provider || !@result_copy
|
164
|
+
|
161
165
|
const_get(:Result).class_eval "#{@result_copy}\nend", __FILE__, __LINE__
|
162
166
|
@finalized_result_provider = true
|
163
167
|
end
|
164
168
|
|
165
|
-
def needs_code
|
169
|
+
def needs_code # rubocop:disable Metrics/MethodLength
|
166
170
|
needs.map do |need|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
171
|
+
if BusinessFlow.active_model5?
|
172
|
+
%(if #{need}.nil?
|
173
|
+
errors.add(:#{need}, :invalid, message: 'must not be nil')
|
174
|
+
throw :halt_step
|
175
|
+
end
|
176
|
+
)
|
177
|
+
else
|
178
|
+
%(if #{need}.nil?
|
179
|
+
errors.add(:#{need}, 'must not be nil')
|
180
|
+
throw :halt_step
|
181
|
+
end
|
182
|
+
)
|
183
|
+
end
|
172
184
|
end.join("\n")
|
173
185
|
end
|
174
186
|
|
175
187
|
def finalize_initializer
|
176
188
|
return if @finalized_initializer
|
189
|
+
|
177
190
|
class_eval %{
|
178
191
|
private def _business_flow_dsl_initialize(parameter_object)
|
179
192
|
@parameter_object = parameter_object
|
@@ -188,7 +201,7 @@ module BusinessFlow
|
|
188
201
|
FROM_FLOW_PREAMBLE = %(
|
189
202
|
def from_flow(flow)
|
190
203
|
return if errors?
|
191
|
-
)
|
204
|
+
)
|
192
205
|
|
193
206
|
RESULT_DEF = %(
|
194
207
|
class Result
|
@@ -216,7 +229,7 @@ module BusinessFlow
|
|
216
229
|
!valid?(context)
|
217
230
|
end
|
218
231
|
end
|
219
|
-
)
|
232
|
+
)
|
220
233
|
|
221
234
|
# Provides the minimum necessary methods to support the use of
|
222
235
|
# ActiveModel::Errors
|
@@ -235,13 +248,16 @@ module BusinessFlow
|
|
235
248
|
end
|
236
249
|
|
237
250
|
# :reek:ManualDispatch I have no need to actually call human_attribute_name,
|
251
|
+
# :reek:TooManyStatements Breaking this up would add complexity.
|
238
252
|
# I just need to know if I have to provide my own.
|
239
253
|
def self.included(klass)
|
240
254
|
klass.extend(ClassMethods)
|
241
255
|
klass.class_eval RESULT_DEF, __FILE__, __LINE__
|
242
256
|
klass.extend(ErrorSupport) unless klass.respond_to?(:human_attribute_name)
|
243
257
|
klass.extend(ActiveModel::Naming)
|
244
|
-
|
258
|
+
return if klass.respond_to?(:read_attribute_for_validation)
|
259
|
+
|
260
|
+
klass.include(ErrorSupport::InstanceMethods)
|
245
261
|
end
|
246
262
|
|
247
263
|
attr_reader :parameter_object
|
@@ -249,6 +265,7 @@ module BusinessFlow
|
|
249
265
|
|
250
266
|
def call
|
251
267
|
return if invalid?
|
268
|
+
|
252
269
|
klass = self.class
|
253
270
|
klass.step_executor.new(klass.step_queue, self).call
|
254
271
|
end
|
@@ -261,7 +278,7 @@ module BusinessFlow
|
|
261
278
|
@parameter_object = parameter_object
|
262
279
|
needs.each do |need|
|
263
280
|
if send(need).nil?
|
264
|
-
|
281
|
+
BusinessFlow.add_error(need, :invalid, error_type, 'must not be nil')
|
265
282
|
throw :halt_step
|
266
283
|
end
|
267
284
|
end
|
@@ -274,6 +291,7 @@ module BusinessFlow
|
|
274
291
|
# We only want to fall back to a default if we were
|
275
292
|
# given nil. Other falsy vlues should be directly used.
|
276
293
|
return yield if value.nil? && block_given?
|
294
|
+
|
277
295
|
value
|
278
296
|
end
|
279
297
|
|
@@ -288,11 +306,8 @@ module BusinessFlow
|
|
288
306
|
end
|
289
307
|
|
290
308
|
private def _business_flow_dsl_parameters
|
291
|
-
@_business_flow_dsl_parameters ||=
|
292
|
-
self.class.inputs.all.
|
293
|
-
[input, _business_flow_parameter_inner_fetch(input)]
|
294
|
-
end
|
295
|
-
]
|
309
|
+
@_business_flow_dsl_parameters ||=
|
310
|
+
self.class.inputs.all.to_h { |input| [input, _business_flow_parameter_inner_fetch(input)] }
|
296
311
|
end
|
297
312
|
|
298
313
|
def errors
|
@@ -302,7 +317,7 @@ module BusinessFlow
|
|
302
317
|
def errors?
|
303
318
|
# We're explicitly using the instance variable here so that if no
|
304
319
|
# errors have been created, we don't initialize the error object.
|
305
|
-
@errors
|
320
|
+
@errors&.present?
|
306
321
|
end
|
307
322
|
|
308
323
|
def valid?(_context = nil)
|
@@ -363,6 +378,7 @@ module BusinessFlow
|
|
363
378
|
def self.eval_method(klass, name, str)
|
364
379
|
return if klass.method_defined?(name) ||
|
365
380
|
klass.private_method_defined?(name)
|
381
|
+
|
366
382
|
unsafe_eval_method(klass, name, str)
|
367
383
|
end
|
368
384
|
|
@@ -443,12 +459,10 @@ module BusinessFlow
|
|
443
459
|
end
|
444
460
|
|
445
461
|
def safe_ivar_name
|
446
|
-
@safe_ivar_name ||=
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
.to_sym
|
451
|
-
end
|
462
|
+
@safe_ivar_name ||= "@business_flow_dsl_#{field}"
|
463
|
+
.sub(/\?$/, '_query')
|
464
|
+
.sub(/!$/, '_bang')
|
465
|
+
.to_sym
|
452
466
|
end
|
453
467
|
end
|
454
468
|
|
@@ -475,7 +489,7 @@ module BusinessFlow
|
|
475
489
|
|
476
490
|
def retriever_method_name
|
477
491
|
@retriever_method_name ||=
|
478
|
-
"_business_flow_dsl_execute_step_for_#{@name}"
|
492
|
+
:"_business_flow_dsl_execute_step_for_#{@name}"
|
479
493
|
end
|
480
494
|
end
|
481
495
|
|
@@ -11,7 +11,7 @@ module BusinessFlow
|
|
11
11
|
|
12
12
|
# Contains methods that we add to the DSL
|
13
13
|
module ClassMethods
|
14
|
-
INSTRUMENTATION_PREFIX = 'business_flow'
|
14
|
+
INSTRUMENTATION_PREFIX = 'business_flow'
|
15
15
|
|
16
16
|
def instrument(name, flow)
|
17
17
|
payload = { flow: flow }
|
@@ -33,7 +33,7 @@ module BusinessFlow
|
|
33
33
|
|
34
34
|
def event_name
|
35
35
|
@event_name ||=
|
36
|
-
"#{INSTRUMENTATION_PREFIX}.flow.#{instrumentation_name}"
|
36
|
+
"#{INSTRUMENTATION_PREFIX}.flow.#{instrumentation_name}"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/lib/business_flow/step.rb
CHANGED
@@ -14,12 +14,13 @@ module BusinessFlow
|
|
14
14
|
|
15
15
|
def parameters_from_source(source)
|
16
16
|
return source if inputs.blank?
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
|
18
|
+
inputs.to_h do |input_name, input_value| # rubocop:disable Style/HashTransformValues
|
19
|
+
[
|
20
|
+
input_name,
|
21
|
+
Inputs.process_input(source, input_value)
|
22
|
+
]
|
23
|
+
end
|
23
24
|
end
|
24
25
|
|
25
26
|
def self.process_input(source, input_value)
|
@@ -133,7 +134,7 @@ module BusinessFlow
|
|
133
134
|
Result.new(step_result, outputs, output)
|
134
135
|
end
|
135
136
|
|
136
|
-
|
137
|
+
private
|
137
138
|
|
138
139
|
# :reek:ManualDispatch This is faster.
|
139
140
|
def callable_for(step_result)
|
@@ -160,10 +161,9 @@ module BusinessFlow
|
|
160
161
|
|
161
162
|
def to_unless(cond)
|
162
163
|
if_stmt = Callable.new(cond)
|
163
|
-
|
164
|
+
proc do |instance, input|
|
164
165
|
!if_stmt.call(instance, input)
|
165
166
|
end
|
166
|
-
Callable.new(unless_stmt)
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
@@ -184,9 +184,7 @@ module BusinessFlow
|
|
184
184
|
opts.fetch(:condition) do
|
185
185
|
if_stmts = opts[:if]
|
186
186
|
unless_stmts = opts[:unless]
|
187
|
-
if if_stmts.present? || unless_stmts.present?
|
188
|
-
ConditionList.new(if_stmts, unless_stmts)
|
189
|
-
end
|
187
|
+
ConditionList.new(if_stmts, unless_stmts) if if_stmts.present? || unless_stmts.present?
|
190
188
|
end
|
191
189
|
end
|
192
190
|
end
|
@@ -223,6 +221,7 @@ module BusinessFlow
|
|
223
221
|
|
224
222
|
def output_fields
|
225
223
|
return [] unless outputs
|
224
|
+
|
226
225
|
outputs.values.select { |field| field.is_a?(Symbol) }
|
227
226
|
end
|
228
227
|
|
@@ -232,20 +231,20 @@ module BusinessFlow
|
|
232
231
|
|
233
232
|
private
|
234
233
|
|
235
|
-
PARAMETERS_NO_INPUT = 'parameter_source'
|
234
|
+
PARAMETERS_NO_INPUT = 'parameter_source'
|
236
235
|
PARAMETERS_WITH_INPUT =
|
237
|
-
'@input_object.parameters_from_source(parameter_source)'
|
236
|
+
'@input_object.parameters_from_source(parameter_source)'
|
238
237
|
WITHOUT_CONDITION = %(
|
239
238
|
@result_factory.result(@callable.call(parameter_source, parameters),
|
240
239
|
parameter_source)
|
241
|
-
)
|
240
|
+
)
|
242
241
|
WITH_CONDITION = %(
|
243
242
|
if @condition.call(parameter_source, parameters)
|
244
243
|
#{WITHOUT_CONDITION}
|
245
244
|
else
|
246
245
|
CONDITION_FAILED
|
247
246
|
end
|
248
|
-
)
|
247
|
+
)
|
249
248
|
|
250
249
|
def update_call_method
|
251
250
|
params = @input_object ? PARAMETERS_WITH_INPUT : PARAMETERS_NO_INPUT
|
data/lib/business_flow.rb
CHANGED
@@ -18,4 +18,15 @@ require 'business_flow/instrument'
|
|
18
18
|
|
19
19
|
# Makes the magic happen.
|
20
20
|
module BusinessFlow
|
21
|
+
def self.active_model5?
|
22
|
+
@active_model5_in_use ||= ActiveModel.version >= Gem::Version.new('5.0.0')
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.add_error(errors, field, error_type, message)
|
26
|
+
if active_model5?
|
27
|
+
errors.add(field, error_type, message: message)
|
28
|
+
else
|
29
|
+
errors.add(field, message)
|
30
|
+
end
|
31
|
+
end
|
21
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: business_flow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.19.
|
4
|
+
version: 0.19.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Scarborough
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -50,118 +50,6 @@ dependencies:
|
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '8'
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: rake
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '10.0'
|
60
|
-
type: :development
|
61
|
-
prerelease: false
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
requirements:
|
64
|
-
- - "~>"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '10.0'
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: reek
|
69
|
-
requirement: !ruby/object:Gem::Requirement
|
70
|
-
requirements:
|
71
|
-
- - "~>"
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: '6.1'
|
74
|
-
type: :development
|
75
|
-
prerelease: false
|
76
|
-
version_requirements: !ruby/object:Gem::Requirement
|
77
|
-
requirements:
|
78
|
-
- - "~>"
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: '6.1'
|
81
|
-
- !ruby/object:Gem::Dependency
|
82
|
-
name: retryable
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
84
|
-
requirements:
|
85
|
-
- - "~>"
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
version: 3.0.4
|
88
|
-
type: :development
|
89
|
-
prerelease: false
|
90
|
-
version_requirements: !ruby/object:Gem::Requirement
|
91
|
-
requirements:
|
92
|
-
- - "~>"
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 3.0.4
|
95
|
-
- !ruby/object:Gem::Dependency
|
96
|
-
name: rspec
|
97
|
-
requirement: !ruby/object:Gem::Requirement
|
98
|
-
requirements:
|
99
|
-
- - "~>"
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: '3.0'
|
102
|
-
type: :development
|
103
|
-
prerelease: false
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
requirements:
|
106
|
-
- - "~>"
|
107
|
-
- !ruby/object:Gem::Version
|
108
|
-
version: '3.0'
|
109
|
-
- !ruby/object:Gem::Dependency
|
110
|
-
name: rubocop
|
111
|
-
requirement: !ruby/object:Gem::Requirement
|
112
|
-
requirements:
|
113
|
-
- - "~>"
|
114
|
-
- !ruby/object:Gem::Version
|
115
|
-
version: '0.53'
|
116
|
-
type: :development
|
117
|
-
prerelease: false
|
118
|
-
version_requirements: !ruby/object:Gem::Requirement
|
119
|
-
requirements:
|
120
|
-
- - "~>"
|
121
|
-
- !ruby/object:Gem::Version
|
122
|
-
version: '0.53'
|
123
|
-
- !ruby/object:Gem::Dependency
|
124
|
-
name: rubocop-rspec
|
125
|
-
requirement: !ruby/object:Gem::Requirement
|
126
|
-
requirements:
|
127
|
-
- - "~>"
|
128
|
-
- !ruby/object:Gem::Version
|
129
|
-
version: 1.24.0
|
130
|
-
type: :development
|
131
|
-
prerelease: false
|
132
|
-
version_requirements: !ruby/object:Gem::Requirement
|
133
|
-
requirements:
|
134
|
-
- - "~>"
|
135
|
-
- !ruby/object:Gem::Version
|
136
|
-
version: 1.24.0
|
137
|
-
- !ruby/object:Gem::Dependency
|
138
|
-
name: simplecov
|
139
|
-
requirement: !ruby/object:Gem::Requirement
|
140
|
-
requirements:
|
141
|
-
- - "~>"
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
version: 0.22.0
|
144
|
-
type: :development
|
145
|
-
prerelease: false
|
146
|
-
version_requirements: !ruby/object:Gem::Requirement
|
147
|
-
requirements:
|
148
|
-
- - "~>"
|
149
|
-
- !ruby/object:Gem::Version
|
150
|
-
version: 0.22.0
|
151
|
-
- !ruby/object:Gem::Dependency
|
152
|
-
name: timecop
|
153
|
-
requirement: !ruby/object:Gem::Requirement
|
154
|
-
requirements:
|
155
|
-
- - "~>"
|
156
|
-
- !ruby/object:Gem::Version
|
157
|
-
version: 0.9.1
|
158
|
-
type: :development
|
159
|
-
prerelease: false
|
160
|
-
version_requirements: !ruby/object:Gem::Requirement
|
161
|
-
requirements:
|
162
|
-
- - "~>"
|
163
|
-
- !ruby/object:Gem::Version
|
164
|
-
version: 0.9.1
|
165
53
|
description:
|
166
54
|
email:
|
167
55
|
- alex@teak.io
|
@@ -170,7 +58,11 @@ extensions: []
|
|
170
58
|
extra_rdoc_files: []
|
171
59
|
files:
|
172
60
|
- ".gitignore"
|
61
|
+
- ".reek.yml"
|
173
62
|
- ".rspec"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- ".ruby-gemset"
|
65
|
+
- ".ruby-version"
|
174
66
|
- ".travis.yml"
|
175
67
|
- Gemfile
|
176
68
|
- Gemfile.lock
|
@@ -199,7 +91,8 @@ files:
|
|
199
91
|
homepage: https://teak.io
|
200
92
|
licenses:
|
201
93
|
- MIT
|
202
|
-
metadata:
|
94
|
+
metadata:
|
95
|
+
rubygems_mfa_required: 'true'
|
203
96
|
post_install_message:
|
204
97
|
rdoc_options: []
|
205
98
|
require_paths:
|
@@ -208,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
208
101
|
requirements:
|
209
102
|
- - ">="
|
210
103
|
- !ruby/object:Gem::Version
|
211
|
-
version:
|
104
|
+
version: 2.7.0
|
212
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
106
|
requirements:
|
214
107
|
- - ">="
|