nxt_pipeline 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +27 -29
- data/README.md +312 -93
- data/lib/nxt_pipeline/callbacks.rb +8 -5
- data/lib/nxt_pipeline/error_callback.rb +5 -2
- data/lib/nxt_pipeline/pipeline.rb +128 -56
- data/lib/nxt_pipeline/step.rb +45 -28
- data/lib/nxt_pipeline/version.rb +1 -1
- data/lib/nxt_pipeline.rb +38 -1
- metadata +6 -7
- data/lib/nxt_pipeline/constructor.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5682c4e905d939b057c79b775c8401f1da648a0e5793c1c438dfef3cdb741147
|
4
|
+
data.tar.gz: 8908bf0ab622f472ccdb1a91cce704b0e88181352f4dfa0bb1942a684b60729b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e33df278d8ba33998cc798f6b1a97461360e81af3a0bad83549d74bf00190e8ef7d4aa2459f513bc1e4b3e750785058ae708c90136bfc17bebf0faf79fa7aed0
|
7
|
+
data.tar.gz: '09d1caf2cd41693f56fddbcf6e70e4ba4b2212b7ce9fd3669e736a6afa39245a177d79171a5819bd3842192a5a3927931a4cb99dd86a3b98e24a834e18460218'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## nxt_pipeline 2.0.0 (10.05.2022)
|
2
|
+
|
3
|
+
- Rename `Pipeline.execute` to `Pipeline.call`
|
4
|
+
- Pipeline#call accepts any argument instead of just key word arguments or hashes
|
5
|
+
- Introduce constructor resolvers
|
6
|
+
- Expose :new and :call directly on NxtPipeline instead of only through NxtPipeline::Pipeline class
|
7
|
+
- Change step DSL: Introduce constructor option to specify the constructor to use for a step
|
8
|
+
- Introduce Configurations
|
9
|
+
- Expose step.status and step.meta_data accessors to set status and meta_data of steps in constructors
|
10
|
+
- Change arguments of error callbacks to be error, acc, step instead of acc, step, error and only pass by arity of callback
|
11
|
+
|
1
12
|
## nxt_pipeline 1.0.0 (24.11.2020)
|
2
13
|
|
3
14
|
Replace after and before execute hooks with proper callbacks.
|
data/Gemfile.lock
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nxt_pipeline (
|
4
|
+
nxt_pipeline (2.0.0)
|
5
5
|
activesupport
|
6
6
|
nxt_registry
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activesupport (
|
11
|
+
activesupport (7.0.3)
|
12
12
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
13
|
i18n (>= 1.6, < 2)
|
14
14
|
minitest (>= 5.1)
|
15
15
|
tzinfo (~> 2.0)
|
16
|
-
zeitwerk (~> 2.3)
|
17
16
|
byebug (11.1.3)
|
18
17
|
coderay (1.1.3)
|
19
|
-
concurrent-ruby (1.1.
|
20
|
-
diff-lcs (1.
|
21
|
-
ffi (1.
|
22
|
-
formatador (
|
23
|
-
guard (2.
|
18
|
+
concurrent-ruby (1.1.10)
|
19
|
+
diff-lcs (1.5.0)
|
20
|
+
ffi (1.15.5)
|
21
|
+
formatador (1.1.0)
|
22
|
+
guard (2.18.0)
|
24
23
|
formatador (>= 0.2.4)
|
25
24
|
listen (>= 2.7, < 4.0)
|
26
25
|
lumberjack (>= 1.0.12, < 2.0)
|
27
26
|
nenv (~> 0.1)
|
28
27
|
notiffany (~> 0.0)
|
29
|
-
pry (>= 0.
|
28
|
+
pry (>= 0.13.0)
|
30
29
|
shellany (~> 0.0)
|
31
30
|
thor (>= 0.18.1)
|
32
31
|
guard-compat (1.2.1)
|
@@ -34,19 +33,19 @@ GEM
|
|
34
33
|
guard (~> 2.1)
|
35
34
|
guard-compat (~> 1.1)
|
36
35
|
rspec (>= 2.99.0, < 4.0)
|
37
|
-
i18n (1.
|
36
|
+
i18n (1.10.0)
|
38
37
|
concurrent-ruby (~> 1.0)
|
39
|
-
listen (3.
|
38
|
+
listen (3.7.1)
|
40
39
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
41
40
|
rb-inotify (~> 0.9, >= 0.9.10)
|
42
41
|
lumberjack (1.2.8)
|
43
42
|
method_source (1.0.0)
|
44
|
-
minitest (5.
|
43
|
+
minitest (5.15.0)
|
45
44
|
nenv (0.3.0)
|
46
45
|
notiffany (0.1.3)
|
47
46
|
nenv (~> 0.1)
|
48
47
|
shellany (~> 0.0)
|
49
|
-
nxt_registry (0.3.
|
48
|
+
nxt_registry (0.3.10)
|
50
49
|
activesupport
|
51
50
|
pry (0.13.1)
|
52
51
|
coderay (~> 1.1)
|
@@ -54,30 +53,29 @@ GEM
|
|
54
53
|
pry-byebug (3.9.0)
|
55
54
|
byebug (~> 11.0)
|
56
55
|
pry (~> 0.13.0)
|
57
|
-
rake (13.0.
|
58
|
-
rb-fsevent (0.
|
56
|
+
rake (13.0.6)
|
57
|
+
rb-fsevent (0.11.1)
|
59
58
|
rb-inotify (0.10.1)
|
60
59
|
ffi (~> 1.0)
|
61
|
-
rspec (3.
|
62
|
-
rspec-core (~> 3.
|
63
|
-
rspec-expectations (~> 3.
|
64
|
-
rspec-mocks (~> 3.
|
65
|
-
rspec-core (3.
|
66
|
-
rspec-support (~> 3.
|
67
|
-
rspec-expectations (3.
|
60
|
+
rspec (3.11.0)
|
61
|
+
rspec-core (~> 3.11.0)
|
62
|
+
rspec-expectations (~> 3.11.0)
|
63
|
+
rspec-mocks (~> 3.11.0)
|
64
|
+
rspec-core (3.11.0)
|
65
|
+
rspec-support (~> 3.11.0)
|
66
|
+
rspec-expectations (3.11.0)
|
68
67
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
|
-
rspec-support (~> 3.
|
70
|
-
rspec-mocks (3.
|
68
|
+
rspec-support (~> 3.11.0)
|
69
|
+
rspec-mocks (3.11.1)
|
71
70
|
diff-lcs (>= 1.2.0, < 2.0)
|
72
|
-
rspec-support (~> 3.
|
73
|
-
rspec-support (3.
|
74
|
-
rspec_junit_formatter (0.
|
71
|
+
rspec-support (~> 3.11.0)
|
72
|
+
rspec-support (3.11.0)
|
73
|
+
rspec_junit_formatter (0.5.1)
|
75
74
|
rspec-core (>= 2, < 4, != 2.12.0)
|
76
75
|
shellany (0.0.1)
|
77
|
-
thor (1.1
|
76
|
+
thor (1.2.1)
|
78
77
|
tzinfo (2.0.4)
|
79
78
|
concurrent-ruby (~> 1.0)
|
80
|
-
zeitwerk (2.4.2)
|
81
79
|
|
82
80
|
PLATFORMS
|
83
81
|
ruby
|
data/README.md
CHANGED
@@ -2,7 +2,17 @@
|
|
2
2
|
|
3
3
|
# NxtPipeline
|
4
4
|
|
5
|
-
|
5
|
+
NxtPipeline is an orchestration framework for your service objects or function objects, how I like to call them.
|
6
|
+
Service objects are a very wide spread way of organizing code in the Ruby and Rails communities. Since it's little classes
|
7
|
+
doing one thing you can think of them as function objects and thus they often share a common interface in a project.
|
8
|
+
There are also many frameworks out there that normalize the usage of service objects and provide a specific way
|
9
|
+
of writing service objects and often also allow to orchestrate (reduce) these service objects.
|
10
|
+
Compare [light-service](https://github.com/adomokos/light-service) for instance.
|
11
|
+
|
12
|
+
The idea of NxtPipeline was to build a flexible orchestration framework for service objects without them having to conform
|
13
|
+
to a specific interface. Instead NxtPipeline expects you to specify how to execute different kinds of service objects
|
14
|
+
through so called constructors and thereby does not dictate you how to write your service objects. Nevertheless this still
|
15
|
+
mostly makes sense if your service objects share common interfaces to keep the necessary configuration to a minimum.
|
6
16
|
|
7
17
|
## Installation
|
8
18
|
|
@@ -22,155 +32,260 @@ Or install it yourself as:
|
|
22
32
|
|
23
33
|
## Usage
|
24
34
|
|
25
|
-
###
|
35
|
+
### Example
|
26
36
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
by the step yielded to the constructor.
|
37
|
+
Let's look at an example. Here validator service objects are orchestrated with NxtPipeline to build a validation
|
38
|
+
pipeline. We inject the accumulator `{ value: 'aki', errors: [] }` that is then passed through all validation steps.
|
39
|
+
If an validator returns an error it's added to the array of errors of the accumulator to collect all errors of all steps.
|
31
40
|
|
32
41
|
```ruby
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
class Validator
|
43
|
+
attr_accessor :error
|
44
|
+
end
|
45
|
+
|
46
|
+
class TypeChecker < Validator
|
47
|
+
def initialize(value, type:)
|
48
|
+
@value = value
|
49
|
+
@type = type
|
41
50
|
end
|
42
51
|
|
43
|
-
|
44
|
-
|
52
|
+
attr_reader :value, :type
|
53
|
+
|
54
|
+
def call
|
55
|
+
return if value.is_a?(type)
|
56
|
+
self.error = "Value does not match type #{type}"
|
45
57
|
end
|
46
58
|
end
|
47
59
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
60
|
+
class MinSize < Validator
|
61
|
+
def initialize(value, size:)
|
62
|
+
@value = value
|
63
|
+
@size = size
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :value, :size
|
67
|
+
|
68
|
+
def call
|
69
|
+
return if value.size >= size
|
70
|
+
self.error = "Value size must be greater than #{size-1}"
|
71
|
+
end
|
52
72
|
end
|
53
73
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
74
|
+
class MaxSize < Validator
|
75
|
+
def initialize(value, size:)
|
76
|
+
@value = value
|
77
|
+
@size = size
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :value, :size
|
81
|
+
|
82
|
+
def call
|
83
|
+
return if value.size <= size
|
84
|
+
self.error = "Value size must be less than #{size+1}"
|
85
|
+
end
|
61
86
|
end
|
87
|
+
|
88
|
+
class Uniqueness < Validator
|
89
|
+
def initialize(value, scope:)
|
90
|
+
@value = value
|
91
|
+
@scope = scope
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_reader :value, :scope
|
95
|
+
|
96
|
+
def call
|
97
|
+
return if scope.count { |item| item == value }
|
98
|
+
self.error = "Value is not unique in: #{scope}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
result = NxtPipeline.call({ value: 'aki', errors: [] }) do |p|
|
103
|
+
p.constructor(:validator, default: true) do |acc, step|
|
104
|
+
validator = step.argument.new(acc.fetch(:value), **step.options)
|
105
|
+
validator.call
|
106
|
+
acc[:errors] << validator.error if validator.error.present?
|
107
|
+
|
108
|
+
acc
|
109
|
+
end
|
110
|
+
|
111
|
+
p.step TypeChecker, options: { type: String }
|
112
|
+
p.step MinSize, options: { size: 4 }
|
113
|
+
p.step MaxSize, options: { size: 10 }
|
114
|
+
p.step Uniqueness, options: { scope: ['andy', 'aki', 'lütfi', 'rapha'] }
|
115
|
+
end
|
116
|
+
|
117
|
+
result # => { value: 'aki', errors: ['Value size must be greater than 3'] }
|
62
118
|
```
|
63
119
|
|
64
|
-
###
|
120
|
+
### Constructors
|
121
|
+
|
122
|
+
In order to reduce over your service objects you have to define constructors so that the pipeline knows how to execute
|
123
|
+
a specific step. You can define constructors globally and specific to a pipeline.
|
124
|
+
|
125
|
+
Make a constructor available for all pipelines of your project by defining it globally with:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
NxtPipeline.constructor(:service) do |acc, step|
|
129
|
+
validator = step.argument.new(acc.fetch(:value), **step.options)
|
130
|
+
validator.call
|
131
|
+
acc[:errors] << validator.error if validator.error.present?
|
132
|
+
|
133
|
+
acc
|
134
|
+
end
|
135
|
+
```
|
65
136
|
|
66
|
-
|
137
|
+
Or define a constructor only locally for a specific pipeline.
|
67
138
|
|
68
139
|
```ruby
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
140
|
+
NxtPipeline.new({ value: 'aki', errors: [] }) do |p|
|
141
|
+
p.constructor(:validator, default: true) do |acc, step|
|
142
|
+
validator = step.argument.new(acc.fetch(:value), **step.options)
|
143
|
+
validator.call
|
144
|
+
acc[:errors] << validator.error if validator.error.present?
|
145
|
+
|
146
|
+
acc
|
147
|
+
end
|
74
148
|
|
75
|
-
|
149
|
+
p.step TypeChecker, options: { type: String }
|
76
150
|
# ...
|
77
151
|
end
|
152
|
+
```
|
153
|
+
|
154
|
+
Constructor Hierarchy
|
78
155
|
|
79
|
-
|
80
|
-
|
156
|
+
In order to execute a specific step the pipeline firstly checks whether a constructor was specified for a step:
|
157
|
+
`pipeline.step MyServiceClass, constructor: :service`. If this is not the case it checks whether there is a resolver
|
158
|
+
registered that applies. If that's not the case the pipeline checks if there is a constructor registered for the
|
159
|
+
argument that was passed in. This means if you register constructors directly for the arguments you pass in you don't
|
160
|
+
have to specify this constructor option. Therefore the following would work without the need to provide a constructor
|
161
|
+
for the steps.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
NxtPipeline.new({}) do |p|
|
165
|
+
p.constructor(:service) do |acc, step|
|
166
|
+
step.service_class.new(acc).call
|
167
|
+
end
|
168
|
+
|
169
|
+
p.step :service, service_class: MyServiceClass
|
170
|
+
p.step :service, service_class: MyOtherServiceClass
|
171
|
+
# ...
|
81
172
|
end
|
82
173
|
```
|
83
174
|
|
84
|
-
|
85
|
-
|
86
|
-
will be set to :inline.
|
175
|
+
Lastly if no constructor could be resolved directly from the step argument, the pipelines falls back to the locally
|
176
|
+
and then to the globally defined default constructors.
|
87
177
|
|
88
|
-
###
|
178
|
+
### Defining steps
|
89
179
|
|
90
|
-
|
180
|
+
Once your pipeline knows how to execute your steps you can add those. The `pipeline.step` method expects at least one
|
181
|
+
argument which you can access in the constructor through `step.argument`. You can also pass in additional options
|
182
|
+
that you can access through readers of a step. The `constructor:` option defines which constructor to use for a step
|
183
|
+
where as you can name a step with the `to_s:` option.
|
91
184
|
|
92
185
|
```ruby
|
93
|
-
|
186
|
+
# explicitly define which constructor to use
|
187
|
+
pipeline.step MyServiceClass, constructor: :service
|
188
|
+
# use a block as inline constructor
|
189
|
+
pipeline.step SpecialService, constructor: ->(step, arg:) { step.argument.call(arg: arg) }
|
190
|
+
# Rely on the default constructor
|
191
|
+
pipeline.step MyOtherServiceClass
|
192
|
+
# Define a step name
|
193
|
+
pipeline.step MyOtherServiceClass, to_s: 'First Step'
|
194
|
+
# Or simply execute a (named) block - NO NEED TO DEFINE A CONSTRUCTOR HERE
|
195
|
+
pipeline.step :step_name_for_better_log do |acc, step|
|
196
|
+
# ...
|
197
|
+
end
|
198
|
+
```
|
94
199
|
|
95
|
-
|
200
|
+
Defining multiple steps at once. This is especially useful to dynamically configure a pipeline for execution and
|
201
|
+
can potentially even come from a yaml configuration or from the database.
|
96
202
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
203
|
+
```ruby
|
204
|
+
pipeline.steps([
|
205
|
+
[MyServiceClass, constructor: :service],
|
206
|
+
[MyOtherServiceClass, constructor: :service],
|
207
|
+
[MyJobClass, constructor: :job]
|
208
|
+
])
|
209
|
+
|
210
|
+
# You can also overwrite the steps of a pipeline through explicitly setting them. This will remove any previously
|
211
|
+
# defined steps.
|
212
|
+
pipeline.steps = [
|
213
|
+
[MyServiceClass, constructor: :service],
|
214
|
+
[MyOtherServiceClass, constructor: :service]
|
215
|
+
]
|
216
|
+
```
|
217
|
+
|
218
|
+
### Execution
|
103
219
|
|
220
|
+
Once a pipeline contains steps you can call it with `call(accumulator)` whereas it expects you to inject the accumulator
|
221
|
+
as argument that is then passed through all steps.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
pipeline.call(arg: 'initial argument')
|
225
|
+
|
226
|
+
# Or directly pass the steps you want to execute:
|
227
|
+
pipeline.call(arg: 'initial argument') do |p|
|
228
|
+
p.step MyServiceClass, to_s: 'First step'
|
229
|
+
p.step MyOtherServiceClass, to_s: 'Second step'
|
230
|
+
p.step MyJobClass, constructor: :job
|
231
|
+
p.step MyOtherJobClass, constructor: :job
|
232
|
+
end
|
104
233
|
```
|
105
234
|
|
106
|
-
You can also
|
235
|
+
You can also create a new instance of a pipeline and directly run it with `call`:
|
107
236
|
|
108
237
|
```ruby
|
109
|
-
NxtPipeline
|
110
|
-
p.
|
111
|
-
{ arg: arg.upcase }
|
112
|
-
end
|
238
|
+
NxtPipeline.call(arg: 'initial argument') do |p|
|
239
|
+
p.steps # ...
|
113
240
|
end
|
114
241
|
```
|
115
242
|
|
116
243
|
You can query the steps of your pipeline simply by calling `pipeline.steps`. A NxtPipeline::Step will provide you with
|
117
|
-
an interface
|
244
|
+
an interface for options, status, execution_finished_at execution_started_at,
|
245
|
+
execution_duration, result, error and the index in the pipeline.
|
118
246
|
|
119
247
|
```
|
120
248
|
pipeline.steps.first
|
121
|
-
# will give you
|
122
|
-
|
123
|
-
#<NxtPipeline::Step:0x00007f83eb399448
|
124
|
-
@constructor=
|
125
|
-
#<Proc:0x00007f83eb399498@/Users/andy/workspace/nxt_pipeline/spec/pipeline_spec.rb:467>,
|
126
|
-
@error=nil,
|
127
|
-
@index=0,
|
128
|
-
@opts={:to_s=>:transformer, :method=>:upcase},
|
129
|
-
@result=nil,
|
130
|
-
@status=nil,
|
131
|
-
@type=:transformer
|
132
|
-
@execution_duration=1.0e-05,
|
133
|
-
@execution_finished_at=2020-10-22 15:52:55.806417 +0100,
|
134
|
-
@execution_started_at=2020-10-22 15:52:55.806407 +0100,>
|
249
|
+
# will give you a step object
|
250
|
+
#<NxtPipeline::Step:0x00007f83eb399448...>
|
135
251
|
```
|
136
252
|
|
137
253
|
### Guard clauses
|
138
254
|
|
139
255
|
You can also define guard clauses that take a proc to prevent the execution of a step.
|
140
|
-
|
256
|
+
A guard can accept the change set and the step as arguments.
|
141
257
|
|
142
258
|
```ruby
|
143
|
-
pipeline.
|
144
|
-
|
145
|
-
|
146
|
-
|
259
|
+
pipeline.call('initial argument') do |p|
|
260
|
+
p.step MyServiceClass, if: -> (acc, step) { acc == 'initial argument' }
|
261
|
+
p.step MyOtherServiceClass, unless: -> { false }
|
262
|
+
end
|
147
263
|
|
148
264
|
```
|
149
265
|
|
150
266
|
### Error callbacks
|
151
267
|
|
152
|
-
Apart from defining constructors and steps you can also define error callbacks.
|
268
|
+
Apart from defining constructors and steps you can also define error callbacks. Error callbacks can accept up to
|
269
|
+
three arguments: `error, acc, step`.
|
153
270
|
|
154
271
|
```ruby
|
155
|
-
NxtPipeline
|
156
|
-
p.step
|
157
|
-
{ arg: arg.upcase }
|
158
|
-
end
|
272
|
+
NxtPipeline.new do |p|
|
273
|
+
p.step # ...
|
159
274
|
|
160
|
-
p.on_error MyCustomError do |
|
275
|
+
p.on_error MyCustomError do |error|
|
161
276
|
# First matching error callback will be executed!
|
162
277
|
end
|
163
278
|
|
164
|
-
p.on_errors ArgumentError, KeyError do |
|
279
|
+
p.on_errors ArgumentError, KeyError do |error, acc|
|
165
280
|
# First matching error callback will be executed!
|
166
281
|
end
|
167
282
|
|
168
|
-
p.on_errors YetAnotherError, halt_on_error: false do |
|
283
|
+
p.on_errors YetAnotherError, halt_on_error: false do |error, acc, step|
|
169
284
|
# After executing the callback the pipeline will not halt but continue to
|
170
285
|
# execute the next steps.
|
171
286
|
end
|
172
287
|
|
173
|
-
p.on_errors do |
|
288
|
+
p.on_errors do |error, acc, step|
|
174
289
|
# This will match all errors inheriting from StandardError
|
175
290
|
end
|
176
291
|
end
|
@@ -179,12 +294,13 @@ end
|
|
179
294
|
### Before, around and after callbacks
|
180
295
|
|
181
296
|
You can also define callbacks :before, :around and :after each step and or the `#execute` method. You can also register
|
182
|
-
multiple callbacks, but probably you want to keep them to a minimum to not end up in hell.
|
297
|
+
multiple callbacks, but probably you want to keep them to a minimum to not end up in hell. Also note that before and
|
298
|
+
after callbacks will run even if a step was skipped through a guard clause.
|
183
299
|
|
184
300
|
#### Step callbacks
|
185
301
|
|
186
302
|
```ruby
|
187
|
-
NxtPipeline
|
303
|
+
NxtPipeline.new do |p|
|
188
304
|
p.before_step do |_, change_set|
|
189
305
|
change_set[:acc] << 'before step 1'
|
190
306
|
change_set
|
@@ -207,7 +323,7 @@ end
|
|
207
323
|
#### Execution callbacks
|
208
324
|
|
209
325
|
```ruby
|
210
|
-
NxtPipeline
|
326
|
+
NxtPipeline.new do |p|
|
211
327
|
p.before_execution do |_, change_set|
|
212
328
|
change_set[:acc] << 'before execution 1'
|
213
329
|
change_set
|
@@ -227,18 +343,121 @@ NxtPipeline::Pipeline.new do |p|
|
|
227
343
|
end
|
228
344
|
```
|
229
345
|
|
230
|
-
Note that the `after_execute` callback will not be called in case a step raises an error.
|
346
|
+
Note that the `after_execute` callback will not be called in case a step raises an error.
|
231
347
|
See the previous section (_Error callbacks_) for how to define callbacks that run in case of errors.
|
232
348
|
|
233
|
-
###
|
349
|
+
### Constructor resolvers
|
350
|
+
|
351
|
+
You can also define constructor resolvers for a pipeline to dynamically define which previously registered constructor
|
352
|
+
to use for a step based on the argument and options passed to the step.
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
class Transform
|
356
|
+
def initialize(word, operation)
|
357
|
+
@word = word
|
358
|
+
@operation = operation
|
359
|
+
end
|
360
|
+
|
361
|
+
attr_reader :word, :operation
|
362
|
+
|
363
|
+
def call
|
364
|
+
word.send(operation)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
NxtPipeline.new do |pipeline|
|
369
|
+
# dynamically resolve to use a proc as constructor
|
370
|
+
pipeline.constructor_resolver do |argument, **opts|
|
371
|
+
argument.is_a?(Class) &&
|
372
|
+
->(step, arg:) {
|
373
|
+
result = step.argument.new(arg, opts.fetch(:operation)).call
|
374
|
+
# OR result = step.argument.new(arg, step.operation).call
|
375
|
+
{ arg: result }
|
376
|
+
}
|
377
|
+
end
|
378
|
+
|
379
|
+
# dynamically resolve to a defined constructor
|
380
|
+
pipeline.constructor_resolver do |argument|
|
381
|
+
argument.is_a?(String) && :dynamic
|
382
|
+
end
|
383
|
+
|
384
|
+
pipeline.constructor(:dynamic) do |step, arg:|
|
385
|
+
if step.argument == 'multiply'
|
386
|
+
{ arg: arg * step.multiplier }
|
387
|
+
elsif step.argument == 'symbolize'
|
388
|
+
{ arg: arg.to_sym }
|
389
|
+
else
|
390
|
+
raise ArgumentError, "Don't know how to deal with argument: #{step.argument}"
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
pipeline.step Transform, operation: 'upcase'
|
395
|
+
pipeline.step 'multiply', multiplier: 2
|
396
|
+
pipeline.step 'symbolize'
|
397
|
+
pipeline.step :extract_value do |arg|
|
398
|
+
arg
|
399
|
+
end
|
400
|
+
end
|
401
|
+
```
|
402
|
+
|
403
|
+
### Configurations
|
404
|
+
|
405
|
+
You probably do not have that many different kinds of steps that you execute within your pipelines. Otherwise the whole
|
406
|
+
concept does not make much sense. To make constructing a pipeline simpler you can therefore define configurations on
|
407
|
+
a global level simply by providing a name for a configuration along with a configuration block.
|
408
|
+
Then you then create a preconfigure pipeline by passing in the name of the configuration when creating a new pipeline.
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
# Define configurations in your initializer or somewhere upfront
|
412
|
+
NxtPipeline.configuration(:test_processor) do |pipeline|
|
413
|
+
pipeline.constructor(:processor) do |arg, step|
|
414
|
+
{ arg: step.argument.call(arg: arg) }
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
NxtPipeline.configure(:validator) do |pipeline|
|
419
|
+
pipeline.constructor(:validator) do |arg, step|
|
420
|
+
# ..
|
421
|
+
end
|
422
|
+
end
|
234
423
|
|
235
|
-
|
236
|
-
You can also use this if you are not fine with resolving the constructor from the step argument. Check out the
|
237
|
-
`nxt_pipeline/spec/step_resolver_spec.rb` for examples how you can implement your own step_resolvers.
|
424
|
+
# ...
|
238
425
|
|
426
|
+
# Later create a pipeline with a previously defined configuration
|
427
|
+
NxtPipeline.new(configuration: :test_processor) do |p|
|
428
|
+
p.step ->(arg) { arg + 'first ' }, constructor: :processor
|
429
|
+
p.step ->(arg) { arg + 'second ' }, constructor: :processor
|
430
|
+
p.step ->(arg) { arg + 'third' }, constructor: :processor
|
431
|
+
end
|
432
|
+
```
|
433
|
+
|
434
|
+
### Step status and meta_data
|
435
|
+
When executing your steps you can also log the status of a step by setting it in your constructors or callbacks in
|
436
|
+
which you have access to the steps.
|
437
|
+
|
438
|
+
```ruby
|
439
|
+
pipeline = NxtPipeline.new do |pipeline|
|
440
|
+
pipeline.constructor(:step, default: true) do |acc, step|
|
441
|
+
result = step.proc.call(acc)
|
442
|
+
step.status = result.present? # Set the status here
|
443
|
+
step.meta_data = 'additional info' # or some meta data
|
444
|
+
acc
|
445
|
+
end
|
446
|
+
|
447
|
+
pipeline.step :first_step do |acc, step|
|
448
|
+
step.status = 'it worked'
|
449
|
+
step.meta_data = { extra: 'info' }
|
450
|
+
acc
|
451
|
+
end
|
452
|
+
|
453
|
+
pipeline.step :second, proc: ->(acc) { acc }
|
454
|
+
end
|
455
|
+
|
456
|
+
pipeline.logger.log # => { "first_step" => 'it worked', "second" => true }
|
457
|
+
pipeline.steps.map(&:meta_data) # => [{:extra=>"info"}, "additional info"]
|
458
|
+
```
|
239
459
|
|
240
460
|
## Topics
|
241
|
-
- Constructors should take arg as first and step as second arg
|
242
461
|
|
243
462
|
## Development
|
244
463
|
|
@@ -10,8 +10,11 @@ module NxtPipeline
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def run(kind_of_callback, type, change_set)
|
13
|
-
registry.resolve!(kind_of_callback, type)
|
14
|
-
|
13
|
+
callbacks = registry.resolve!(kind_of_callback, type)
|
14
|
+
return unless callbacks.any?
|
15
|
+
|
16
|
+
callbacks.inject(change_set) do |changes, callback|
|
17
|
+
run_callback(callback, changes)
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -20,7 +23,7 @@ module NxtPipeline
|
|
20
23
|
return execution.call unless around_callbacks.any?
|
21
24
|
|
22
25
|
callback_chain = around_callbacks.reverse.inject(execution) do |previous, callback|
|
23
|
-
-> { callback.call(
|
26
|
+
-> { callback.call(change_set, previous, pipeline) }
|
24
27
|
end
|
25
28
|
|
26
29
|
callback_chain.call
|
@@ -31,8 +34,8 @@ module NxtPipeline
|
|
31
34
|
attr_reader :registry, :pipeline
|
32
35
|
|
33
36
|
def run_callback(callback, change_set)
|
34
|
-
args = [
|
35
|
-
args = args.take(callback.arity)
|
37
|
+
args = [change_set, pipeline]
|
38
|
+
args = args.take(callback.arity) unless callback.arity.negative?
|
36
39
|
callback.call(*args)
|
37
40
|
end
|
38
41
|
|
@@ -20,8 +20,11 @@ module NxtPipeline
|
|
20
20
|
(error.class.ancestors & errors).any?
|
21
21
|
end
|
22
22
|
|
23
|
-
def call(
|
24
|
-
|
23
|
+
def call(error, acc, step)
|
24
|
+
args = [error, acc, step]
|
25
|
+
args = args.take(callback.arity) unless callback.arity.negative?
|
26
|
+
|
27
|
+
callback.call(*args)
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module NxtPipeline
|
2
2
|
class Pipeline
|
3
|
-
def self.
|
4
|
-
new(&block).
|
3
|
+
def self.call(acc, configuration: nil, resolvers: [], &block)
|
4
|
+
new(configuration: configuration, resolvers: resolvers, &block).call(acc)
|
5
5
|
end
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(configuration: nil, resolvers: [], &block)
|
8
8
|
@steps = []
|
9
9
|
@error_callbacks = []
|
10
10
|
@logger = Logger.new
|
@@ -12,27 +12,34 @@ module NxtPipeline
|
|
12
12
|
@current_arg = nil
|
13
13
|
@default_constructor_name = nil
|
14
14
|
@constructors = {}
|
15
|
-
@
|
15
|
+
@constructor_resolvers = resolvers
|
16
|
+
@result = nil
|
17
|
+
|
18
|
+
if configuration.present?
|
19
|
+
config = ::NxtPipeline.configuration(configuration)
|
20
|
+
configure(&config)
|
21
|
+
end
|
16
22
|
|
17
23
|
configure(&block) if block_given?
|
18
24
|
end
|
19
25
|
|
20
26
|
alias_method :configure, :tap
|
21
27
|
|
22
|
-
attr_accessor :logger
|
28
|
+
attr_accessor :logger
|
29
|
+
attr_reader :result
|
23
30
|
|
24
|
-
def constructor(name,
|
31
|
+
def constructor(name, default: false, &constructor)
|
25
32
|
name = name.to_sym
|
26
33
|
raise StandardError, "Already registered step :#{name}" if constructors[name]
|
27
34
|
|
28
|
-
constructors[name] =
|
35
|
+
constructors[name] = constructor
|
29
36
|
|
30
|
-
return unless
|
37
|
+
return unless default
|
31
38
|
set_default_constructor(name)
|
32
39
|
end
|
33
40
|
|
34
|
-
def
|
35
|
-
|
41
|
+
def constructor_resolver(&block)
|
42
|
+
constructor_resolvers << block
|
36
43
|
end
|
37
44
|
|
38
45
|
def set_default_constructor(default_constructor)
|
@@ -44,61 +51,128 @@ module NxtPipeline
|
|
44
51
|
raise ArgumentError, 'Default step already defined'
|
45
52
|
end
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
# Overwrite reader to also define steps
|
55
|
+
def steps(steps = [])
|
56
|
+
return @steps unless steps.any?
|
57
|
+
|
58
|
+
steps.each do |args|
|
59
|
+
arguments = Array(args)
|
60
|
+
if arguments.size == 1
|
61
|
+
step(arguments.first)
|
62
|
+
elsif arguments.size == 2
|
63
|
+
step(arguments.first, **arguments.second)
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Either pass a single argument or an argument and options"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Allow to force steps with setter
|
71
|
+
def steps=(steps = [])
|
72
|
+
# reset steps to be zero
|
73
|
+
@steps = []
|
74
|
+
steps(steps)
|
75
|
+
end
|
76
|
+
|
77
|
+
def step(argument, constructor: nil, **opts, &block)
|
78
|
+
|
79
|
+
if constructor.present? and block_given?
|
80
|
+
msg = "Either specify a block or a constructor but not both at the same time!"
|
81
|
+
raise ArgumentError, msg
|
82
|
+
end
|
83
|
+
|
84
|
+
to_s = if opts[:to_s].present?
|
85
|
+
opts[:to_s] = opts[:to_s].to_s
|
86
|
+
else
|
87
|
+
if argument.is_a?(Proc) || argument.is_a?(Method)
|
88
|
+
steps.count.to_s
|
89
|
+
else
|
90
|
+
argument.to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
opts.reverse_merge!(to_s: to_s)
|
96
|
+
|
97
|
+
if constructor.present?
|
98
|
+
# p.step Service, constructor: ->(step, **changes) { ... }
|
99
|
+
if constructor.respond_to?(:call)
|
100
|
+
resolved_constructor = constructor
|
101
|
+
else
|
102
|
+
# p.step Service, constructor: :service
|
103
|
+
resolved_constructor = constructors.fetch(constructor) {
|
104
|
+
::NxtPipeline.constructor(constructor) || (raise ArgumentError, "No constructor defined for #{constructor}")
|
105
|
+
}
|
106
|
+
end
|
107
|
+
elsif block_given?
|
108
|
+
# p.step :inline do ... end
|
109
|
+
resolved_constructor = block
|
53
110
|
else
|
54
|
-
constructor
|
55
|
-
|
111
|
+
# If no constructor was given try to resolve one
|
112
|
+
resolvers = constructor_resolvers.any? ? constructor_resolvers : []
|
113
|
+
|
114
|
+
constructor_from_resolvers = resolvers.map do |resolver|
|
115
|
+
resolver.call(argument, **opts)
|
56
116
|
end.find(&:itself)
|
57
117
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
118
|
+
# resolved constructor is a proc
|
119
|
+
if constructor_from_resolvers.is_a?(Proc)
|
120
|
+
resolved_constructor = constructor_from_resolvers
|
121
|
+
elsif constructor_from_resolvers.present?
|
122
|
+
resolved_constructor = constructors[constructor_from_resolvers]
|
63
123
|
else
|
64
|
-
|
124
|
+
# try to resolve constructor by argument --> #TODO: Is this a good idea?
|
125
|
+
resolved_constructor = constructors[argument]
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# if still no constructor resolved
|
130
|
+
unless resolved_constructor.present?
|
131
|
+
if argument.is_a?(NxtPipeline::Pipeline)
|
132
|
+
pipeline_constructor = ->(changes) { argument.call(changes) }
|
133
|
+
resolved_constructor = pipeline_constructor
|
134
|
+
# last chance: default constructor
|
135
|
+
elsif default_constructor.present?
|
136
|
+
resolved_constructor = default_constructor
|
137
|
+
# now we really give up :-(
|
138
|
+
else
|
139
|
+
raise ArgumentError, "Could neither find nor resolve any constructor for #{argument}, #{opts}"
|
140
|
+
end
|
65
141
|
end
|
66
142
|
end
|
67
143
|
|
68
|
-
register_step(argument,
|
144
|
+
register_step(argument, resolved_constructor, callbacks, **opts)
|
69
145
|
end
|
70
146
|
|
71
|
-
def
|
147
|
+
def call(acc, &block)
|
72
148
|
reset
|
73
149
|
|
74
150
|
configure(&block) if block_given?
|
75
|
-
callbacks.run(:before, :execution,
|
151
|
+
callbacks.run(:before, :execution, acc)
|
76
152
|
|
77
|
-
result = callbacks.around :execution,
|
78
|
-
steps.inject(
|
79
|
-
|
153
|
+
self.result = callbacks.around :execution, acc do
|
154
|
+
steps.inject(acc) do |changes, step|
|
155
|
+
self.result = call_step(step, changes)
|
80
156
|
rescue StandardError => error
|
81
|
-
decorate_error_with_details(error,
|
82
|
-
handle_error_of_step(error)
|
83
|
-
|
157
|
+
decorate_error_with_details(error, changes, step, logger)
|
158
|
+
handle_error_of_step(step, error)
|
159
|
+
result
|
84
160
|
end
|
85
161
|
end
|
86
162
|
|
87
|
-
callbacks.run(:after, :execution,
|
88
|
-
result
|
163
|
+
# callbacks.run(:after, :execution, result) TODO: Better pass result to callback?
|
164
|
+
self.result = callbacks.run(:after, :execution, acc) || result
|
89
165
|
rescue StandardError => error
|
90
166
|
handle_step_error(error)
|
91
167
|
end
|
92
168
|
|
93
|
-
alias_method :call, :execute
|
94
|
-
|
95
169
|
def handle_step_error(error)
|
96
170
|
log_step(current_step)
|
97
171
|
callback = find_error_callback(error)
|
98
172
|
|
99
173
|
raise unless callback
|
100
174
|
|
101
|
-
callback.call(
|
175
|
+
callback.call(error, current_arg, current_step)
|
102
176
|
end
|
103
177
|
|
104
178
|
def on_errors(*errors, halt_on_error: true, &callback)
|
@@ -133,14 +207,15 @@ module NxtPipeline
|
|
133
207
|
|
134
208
|
private
|
135
209
|
|
210
|
+
attr_writer :result
|
211
|
+
|
136
212
|
def callbacks
|
137
213
|
@callbacks ||= NxtPipeline::Callbacks.new(pipeline: self)
|
138
214
|
end
|
139
215
|
|
140
|
-
attr_reader :error_callbacks, :constructors, :
|
141
|
-
|
142
|
-
|
143
|
-
:default_constructor_name
|
216
|
+
attr_reader :error_callbacks, :constructors, :constructor_resolvers
|
217
|
+
attr_writer :default_constructor_name
|
218
|
+
attr_accessor :current_step, :current_arg
|
144
219
|
|
145
220
|
def default_constructor
|
146
221
|
return unless default_constructor_name
|
@@ -148,12 +223,12 @@ module NxtPipeline
|
|
148
223
|
@default_constructor ||= constructors[default_constructor_name.to_sym]
|
149
224
|
end
|
150
225
|
|
151
|
-
def
|
226
|
+
def call_step(step, acc)
|
152
227
|
self.current_step = step
|
153
|
-
self.current_arg =
|
154
|
-
result = step.
|
228
|
+
self.current_arg = acc
|
229
|
+
result = step.call(acc)
|
155
230
|
log_step(step)
|
156
|
-
result ||
|
231
|
+
result || acc
|
157
232
|
end
|
158
233
|
|
159
234
|
def find_error_callback(error)
|
@@ -171,13 +246,6 @@ module NxtPipeline
|
|
171
246
|
self.current_step = nil
|
172
247
|
end
|
173
248
|
|
174
|
-
def raise_reserved_type_inline_error
|
175
|
-
raise ArgumentError, 'Type :inline is reserved for inline steps!'
|
176
|
-
end
|
177
|
-
|
178
|
-
def default_step_resolvers
|
179
|
-
[->(step_argument) { step_argument.is_a?(Symbol) && step_argument }]
|
180
|
-
end
|
181
249
|
|
182
250
|
def decorate_error_with_details(error, change_set, step, logger)
|
183
251
|
error.define_singleton_method :details do
|
@@ -194,14 +262,18 @@ module NxtPipeline
|
|
194
262
|
steps << Step.new(argument, constructor, steps.count, self, callbacks, **opts)
|
195
263
|
end
|
196
264
|
|
197
|
-
def handle_error_of_step(error)
|
265
|
+
def handle_error_of_step(step, error)
|
198
266
|
error_callback = find_error_callback(error)
|
199
267
|
raise error unless error_callback.present? && error_callback.continue_after_error?
|
200
268
|
|
201
|
-
log_step(
|
269
|
+
log_step(step)
|
202
270
|
raise error unless error_callback.present?
|
203
271
|
|
204
|
-
error_callback.call(
|
272
|
+
error_callback.call(error, current_arg, step)
|
273
|
+
end
|
274
|
+
|
275
|
+
def default_constructor_name
|
276
|
+
@default_constructor_name || ::NxtPipeline.default_constructor_name
|
205
277
|
end
|
206
278
|
end
|
207
279
|
end
|
data/lib/nxt_pipeline/step.rb
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
module NxtPipeline
|
2
2
|
class Step
|
3
|
+
RESERVED_OPTION_KEYS = %i[to_s unless if]
|
4
|
+
|
3
5
|
def initialize(argument, constructor, index, pipeline, callbacks, **opts)
|
4
|
-
|
6
|
+
@opts = opts.symbolize_keys
|
5
7
|
|
6
8
|
@pipeline = pipeline
|
7
9
|
@callbacks = callbacks
|
8
10
|
@argument = argument
|
9
11
|
@index = index
|
10
|
-
@opts = opts
|
11
12
|
@constructor = constructor
|
12
|
-
@to_s =
|
13
|
+
@to_s = opts.fetch(:to_s) { argument }
|
13
14
|
@options_mapper = opts[:map_options]
|
14
15
|
|
15
16
|
@status = nil
|
16
17
|
@result = nil
|
17
18
|
@error = nil
|
18
19
|
@mapped_options = nil
|
20
|
+
@meta_data = nil
|
21
|
+
|
22
|
+
define_option_readers
|
19
23
|
end
|
20
24
|
|
21
25
|
attr_reader :argument,
|
22
26
|
:result,
|
23
|
-
:status,
|
24
27
|
:execution_started_at,
|
25
28
|
:execution_finished_at,
|
26
29
|
:execution_duration,
|
@@ -29,25 +32,23 @@ module NxtPipeline
|
|
29
32
|
:index,
|
30
33
|
:mapped_options
|
31
34
|
|
32
|
-
|
35
|
+
attr_writer :to_s
|
36
|
+
attr_accessor :meta_data, :status
|
33
37
|
|
34
|
-
|
35
|
-
alias_method :name, :to_s
|
36
|
-
|
37
|
-
def execute(**change_set)
|
38
|
+
def call(acc)
|
38
39
|
track_execution_time do
|
39
|
-
set_mapped_options(
|
40
|
-
guard_args = [
|
40
|
+
set_mapped_options(acc)
|
41
|
+
guard_args = [acc, self]
|
41
42
|
|
42
|
-
callbacks.run(:before, :step,
|
43
|
+
callbacks.run(:before, :step, acc)
|
43
44
|
|
44
45
|
if evaluate_unless_guard(guard_args) && evaluate_if_guard(guard_args)
|
45
|
-
callbacks.around(:step,
|
46
|
-
set_result(
|
46
|
+
callbacks.around(:step, acc) do
|
47
|
+
set_result(acc)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
|
-
callbacks.run(:after, :step,
|
51
|
+
callbacks.run(:after, :step, acc)
|
51
52
|
|
52
53
|
set_status
|
53
54
|
result
|
@@ -58,15 +59,19 @@ module NxtPipeline
|
|
58
59
|
raise
|
59
60
|
end
|
60
61
|
|
61
|
-
def set_mapped_options(
|
62
|
+
def set_mapped_options(acc)
|
62
63
|
mapper = options_mapper || default_options_mapper
|
63
|
-
mapper_args = [
|
64
|
+
mapper_args = [acc, self].take(mapper.arity)
|
64
65
|
self.mapped_options = mapper.call(*mapper_args)
|
65
66
|
end
|
66
67
|
|
68
|
+
def to_s
|
69
|
+
@to_s.to_s
|
70
|
+
end
|
71
|
+
|
67
72
|
private
|
68
73
|
|
69
|
-
attr_writer :result, :
|
74
|
+
attr_writer :result, :error, :mapped_options, :execution_started_at, :execution_finished_at, :execution_duration
|
70
75
|
attr_reader :constructor, :options_mapper, :pipeline, :callbacks
|
71
76
|
|
72
77
|
def evaluate_if_guard(args)
|
@@ -77,19 +82,15 @@ module NxtPipeline
|
|
77
82
|
!execute_callable(unless_guard, args)
|
78
83
|
end
|
79
84
|
|
80
|
-
def set_result(
|
81
|
-
args = [
|
85
|
+
def set_result(acc)
|
86
|
+
args = [acc, self]
|
82
87
|
self.result = execute_callable(constructor, args)
|
83
88
|
end
|
84
89
|
|
85
90
|
def execute_callable(callable, args)
|
86
|
-
args =
|
91
|
+
args = args.take(callable.arity) unless callable.arity.negative?
|
87
92
|
|
88
|
-
|
89
|
-
callable.call(*args.take(args.length - 1), **args.last)
|
90
|
-
else
|
91
|
-
callable.call(*args)
|
92
|
-
end
|
93
|
+
callable.call(*args)
|
93
94
|
end
|
94
95
|
|
95
96
|
def if_guard
|
@@ -104,8 +105,10 @@ module NxtPipeline
|
|
104
105
|
-> { result }
|
105
106
|
end
|
106
107
|
|
107
|
-
def
|
108
|
-
|
108
|
+
def define_option_readers
|
109
|
+
raise ArgumentError, "#{invalid_option_keys} are not allowed as options" if invalid_option_keys.any?
|
110
|
+
|
111
|
+
options_without_reserved_options.each do |key, value|
|
109
112
|
define_singleton_method key.to_s do
|
110
113
|
value
|
111
114
|
end
|
@@ -113,6 +116,8 @@ module NxtPipeline
|
|
113
116
|
end
|
114
117
|
|
115
118
|
def set_status
|
119
|
+
return if status.present? # We do not set it if the constructor did already
|
120
|
+
|
116
121
|
self.status = result.present? ? :success : :skipped
|
117
122
|
end
|
118
123
|
|
@@ -140,5 +145,17 @@ module NxtPipeline
|
|
140
145
|
# returns an empty hash
|
141
146
|
->(_) { {} }
|
142
147
|
end
|
148
|
+
|
149
|
+
def options_without_reserved_options
|
150
|
+
opts.except(*reserved_option_keys)
|
151
|
+
end
|
152
|
+
|
153
|
+
def reserved_option_keys
|
154
|
+
@reserved_option_keys ||= methods + RESERVED_OPTION_KEYS
|
155
|
+
end
|
156
|
+
|
157
|
+
def invalid_option_keys
|
158
|
+
opts.except(*RESERVED_OPTION_KEYS).keys & methods
|
159
|
+
end
|
143
160
|
end
|
144
161
|
end
|
data/lib/nxt_pipeline/version.rb
CHANGED
data/lib/nxt_pipeline.rb
CHANGED
@@ -2,11 +2,48 @@ require 'active_support/all'
|
|
2
2
|
require 'nxt_registry'
|
3
3
|
require 'nxt_pipeline/version'
|
4
4
|
require 'nxt_pipeline/logger'
|
5
|
-
require 'nxt_pipeline/constructor'
|
6
5
|
require 'nxt_pipeline/pipeline'
|
7
6
|
require 'nxt_pipeline/step'
|
8
7
|
require 'nxt_pipeline/callbacks'
|
9
8
|
require 'nxt_pipeline/error_callback'
|
10
9
|
|
11
10
|
module NxtPipeline
|
11
|
+
class << self
|
12
|
+
delegate :new, :call, to: Pipeline
|
13
|
+
end
|
14
|
+
|
15
|
+
def configuration(name, &block)
|
16
|
+
@configurations ||= {}
|
17
|
+
|
18
|
+
if block_given?
|
19
|
+
raise ArgumentError, "Configuration already defined for #{name}" if @configurations[name].present?
|
20
|
+
@configurations[name] = block
|
21
|
+
else
|
22
|
+
@configurations.fetch(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def constructor(name, default: false, &block)
|
27
|
+
@constructors ||= {}
|
28
|
+
|
29
|
+
if block_given?
|
30
|
+
raise ArgumentError, "Constructor already defined for #{name}" if @constructors[name].present?
|
31
|
+
|
32
|
+
default_constructor_name(name) if default
|
33
|
+
@constructors[name] = block
|
34
|
+
else
|
35
|
+
@constructors.fetch(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_constructor_name(name = nil)
|
40
|
+
if name.present?
|
41
|
+
raise ArgumentError, "Default constructor #{@default_constructor_name} defined already" if @default_constructor_name.present?
|
42
|
+
@default_constructor_name = name
|
43
|
+
else
|
44
|
+
@default_constructor_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module_function :configuration, :constructor, :default_constructor_name
|
12
49
|
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nxt_pipeline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nils Sommer
|
8
8
|
- Andreas Robecke
|
9
9
|
- Raphael Kallensee
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2022-05-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -110,7 +110,7 @@ dependencies:
|
|
110
110
|
- - ">="
|
111
111
|
- !ruby/object:Gem::Version
|
112
112
|
version: '0'
|
113
|
-
description:
|
113
|
+
description:
|
114
114
|
email:
|
115
115
|
- mail@nilssommer.de
|
116
116
|
executables: []
|
@@ -136,7 +136,6 @@ files:
|
|
136
136
|
- bin/setup
|
137
137
|
- lib/nxt_pipeline.rb
|
138
138
|
- lib/nxt_pipeline/callbacks.rb
|
139
|
-
- lib/nxt_pipeline/constructor.rb
|
140
139
|
- lib/nxt_pipeline/error_callback.rb
|
141
140
|
- lib/nxt_pipeline/logger.rb
|
142
141
|
- lib/nxt_pipeline/pipeline.rb
|
@@ -150,7 +149,7 @@ metadata:
|
|
150
149
|
allowed_push_host: https://rubygems.org
|
151
150
|
homepage_uri: https://github.com/nxt-insurance/nxt_pipeline
|
152
151
|
source_code_uri: https://github.com/nxt-insurance/nxt_pipeline.git
|
153
|
-
post_install_message:
|
152
|
+
post_install_message:
|
154
153
|
rdoc_options: []
|
155
154
|
require_paths:
|
156
155
|
- lib
|
@@ -166,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
165
|
version: '0'
|
167
166
|
requirements: []
|
168
167
|
rubygems_version: 3.1.2
|
169
|
-
signing_key:
|
168
|
+
signing_key:
|
170
169
|
specification_version: 4
|
171
170
|
summary: DSL to build Pipeline with mountable Segments to process things.
|
172
171
|
test_files: []
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module NxtPipeline
|
2
|
-
class Constructor
|
3
|
-
def initialize(name, **opts, &block)
|
4
|
-
@name = name
|
5
|
-
@block = block
|
6
|
-
@opts = opts
|
7
|
-
end
|
8
|
-
|
9
|
-
attr_reader :opts, :block
|
10
|
-
|
11
|
-
delegate :arity, to: :block
|
12
|
-
|
13
|
-
def call(*args, **opts, &block)
|
14
|
-
# ActiveSupport's #delegate does not properly handle keyword arg passing
|
15
|
-
# in the latest released version. Thefore we bypass delegation by reimplementing
|
16
|
-
# the method ourselves. This is already fixed in Rails master though.
|
17
|
-
self.block.call(*args, **opts, &block)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|