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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77672035013c0cfe29bb5c5057178a9822e25c5b02e83b87a8cbb8dadac1d687
4
- data.tar.gz: bb097fa702a19a9756d86a2ff63792bed998d9f8c5425263864695606bfca70d
3
+ metadata.gz: 5682c4e905d939b057c79b775c8401f1da648a0e5793c1c438dfef3cdb741147
4
+ data.tar.gz: 8908bf0ab622f472ccdb1a91cce704b0e88181352f4dfa0bb1942a684b60729b
5
5
  SHA512:
6
- metadata.gz: 2d355345b5aadcb3d02895cd85196cc60b34caef8c919d11480a5a2cd429c5e32baa37c83f8e169ca316311735343297ba0a3b763044e12ac02c8349cc849929
7
- data.tar.gz: eeaa40df4479d0b4b39e5cfe5fd89756d6681dc3d9e9f96e65f6f1e4f9793c588298c33c0fa446c0f7ddf73a3a9e4a43c5da5f371cf25928f18c80bed52e3e58
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 (1.0.0)
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 (6.1.1)
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.8)
20
- diff-lcs (1.4.4)
21
- ffi (1.14.2)
22
- formatador (0.2.5)
23
- guard (2.16.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.9.12)
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.8.7)
36
+ i18n (1.10.0)
38
37
  concurrent-ruby (~> 1.0)
39
- listen (3.4.1)
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.14.3)
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.6)
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.3)
58
- rb-fsevent (0.10.4)
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.10.0)
62
- rspec-core (~> 3.10.0)
63
- rspec-expectations (~> 3.10.0)
64
- rspec-mocks (~> 3.10.0)
65
- rspec-core (3.10.1)
66
- rspec-support (~> 3.10.0)
67
- rspec-expectations (3.10.1)
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.10.0)
70
- rspec-mocks (3.10.1)
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.10.0)
73
- rspec-support (3.10.1)
74
- rspec_junit_formatter (0.4.1)
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.0)
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
- nxt_pipeline provides a DSL to define pipeline classes which take an object and pass it through multiple steps which can read or modify the object.
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
- ### Constructors
35
+ ### Example
26
36
 
27
- First you probably want to configure a pipeline so that it can execute your steps.
28
- Therefore you want to define constructors for your steps. Constructors take a name
29
- as the first argument and step options as the second. All step options are being exposed
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
- pipeline = NxtPipeline::Pipeline.new do |p|
34
- # Add a named constructor that will be used to execute your steps later
35
- # All options that you pass in your step will be available through accessors in your constructor
36
- # You can call :to_s on a step to set it by default. You can later overwrite at execution for each step if needed.
37
- p.constructor(:service, default: true) do |step, arg:|
38
- step.to_s = step.service_class.to_s
39
- result = step.service_class.new(options: arg).call
40
- result && { arg: result }
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
- p.constructor(:job) do |step, arg:|
44
- step.job_class.perform_later(*arg) && { arg: arg }
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
- # Once a pipeline was created you can still configure it
49
- pipeline.constructor(:call) do |step, arg:|
50
- result = step.caller.new(arg).call
51
- result && { arg: result }
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
- # same with block syntax
55
- # You can use this to split up execution from configuration
56
- pipeline.configure do |p|
57
- p.constructor(:call) do |step, arg:|
58
- result = step.caller.new(arg).call
59
- result && { arg: result }
60
- end
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
- ### Defining steps
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
- Once your pipeline knows how to execute your steps you can add those.
137
+ Or define a constructor only locally for a specific pipeline.
67
138
 
68
139
  ```ruby
69
- pipeline.step :service, service_class: MyServiceClass, to_s: 'First step'
70
- pipeline.step service_class: MyOtherServiceClass, to_s: 'Second step'
71
- # ^ Since service is the default step you don't have to specify it the step type each time
72
- pipeline.step :job, job_class: MyJobClass # to_s is optional
73
- pipeline.step :job, job_class: MyOtherJobClass
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
- pipeline.step :step_name_for_better_log do |_, arg:|
149
+ p.step TypeChecker, options: { type: String }
76
150
  # ...
77
151
  end
152
+ ```
153
+
154
+ Constructor Hierarchy
78
155
 
79
- pipeline.step to_s: 'This is the same as above' do |step, arg:|
80
- # ... step.to_s => 'This is the same as above'
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
- You can also define inline steps, meaning the block will be executed. When you do not provide a :to_s option, type
85
- will be used as :to_s option per default. When no type was given for an inline block the type of the inline block
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
- ### Execution
178
+ ### Defining steps
89
179
 
90
- You can then execute the steps with:
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
- pipeline.execute(arg: 'initial argument')
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
- # Or run the steps directly using block syntax
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
- pipeline.execute(arg: 'initial argument') do |p|
98
- p.step :service, service_class: MyServiceClass, to_s: 'First step'
99
- p.step :service, service_class: MyOtherServiceClass, to_s: 'Second step'
100
- p.step :job, job_class: MyJobClass # to_s is optional
101
- p.step :job, job_class: MyOtherJobClass
102
- end
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 directly execute a pipeline with:
235
+ You can also create a new instance of a pipeline and directly run it with `call`:
107
236
 
108
237
  ```ruby
109
- NxtPipeline::Pipeline.execute(arg: 'initial argument') do |p|
110
- p.step do |_, arg:|
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 to it's type, options, status (:success, :skipped, :failed), execution_finished_at execution_started_at, execution_duration, result, error and the index in the pipeline.
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 something like this:
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
- When the guard takes an argument the step argument is yielded.
256
+ A guard can accept the change set and the step as arguments.
141
257
 
142
258
  ```ruby
143
- pipeline.execute(arg: 'initial argument') do |p|
144
- p.step :service, service_class: MyServiceClass, if: -> (arg:) { arg == 'initial argument' }
145
- p.step :service, service_class: MyOtherServiceClass, unless: -> { false }
146
- end
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::Pipeline.new do |p|
156
- p.step do |_, arg:|
157
- { arg: arg.upcase }
158
- end
272
+ NxtPipeline.new do |p|
273
+ p.step # ...
159
274
 
160
- p.on_error MyCustomError do |step, opts, error|
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 |step, opts, error|
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 |step, opts, error|
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 |step, opts, error|
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::Pipeline.new do |p|
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::Pipeline.new do |p|
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
- ### Step resolvers
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
- NxtPipeline is using so called step_resolvers to find the constructor for a given step by the arguments passed in.
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).each do |callback|
14
- run_callback(callback, change_set)
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(pipeline, change_set, previous) }
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 = [pipeline, change_set]
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(step, arg, error)
24
- callback.call(step, arg, error)
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.execute(**opts, &block)
4
- new(&block).execute(**opts)
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(step_resolvers = default_step_resolvers, &block)
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
- @step_resolvers = step_resolvers
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, :steps
28
+ attr_accessor :logger
29
+ attr_reader :result
23
30
 
24
- def constructor(name, **opts, &constructor)
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] = Constructor.new(name, **opts, &constructor)
35
+ constructors[name] = constructor
29
36
 
30
- return unless opts.fetch(:default, false)
37
+ return unless default
31
38
  set_default_constructor(name)
32
39
  end
33
40
 
34
- def step_resolver(&block)
35
- step_resolvers << block
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
- def step(argument = nil, **opts, &block)
48
- constructor = if block_given?
49
- # make type the :to_s of inline steps fall back to :inline if no type is given
50
- argument ||= :inline
51
- opts.reverse_merge!(to_s: argument)
52
- Constructor.new(:inline, **opts, &block)
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 = step_resolvers.lazy.map do |resolver|
55
- resolver.call(argument)
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
- if constructor
59
- constructor && constructors.fetch(constructor) { raise KeyError, "No step :#{argument} registered" }
60
- elsif default_constructor
61
- argument ||= default_constructor_name
62
- default_constructor
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
- raise StandardError, "Could not resolve step from: #{argument}"
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, constructor, callbacks, **opts)
144
+ register_step(argument, resolved_constructor, callbacks, **opts)
69
145
  end
70
146
 
71
- def execute(**change_set, &block)
147
+ def call(acc, &block)
72
148
  reset
73
149
 
74
150
  configure(&block) if block_given?
75
- callbacks.run(:before, :execution, change_set)
151
+ callbacks.run(:before, :execution, acc)
76
152
 
77
- result = callbacks.around :execution, change_set do
78
- steps.inject(change_set) do |set, step|
79
- execute_step(step, **set)
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, set, step, logger)
82
- handle_error_of_step(error)
83
- set
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, change_set)
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(current_step, current_arg, error)
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, :step_resolvers
141
- attr_accessor :current_step,
142
- :current_arg,
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 execute_step(step, **change_set)
226
+ def call_step(step, acc)
152
227
  self.current_step = step
153
- self.current_arg = change_set
154
- result = step.execute(**change_set)
228
+ self.current_arg = acc
229
+ result = step.call(acc)
155
230
  log_step(step)
156
- result || change_set
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(current_step)
269
+ log_step(step)
202
270
  raise error unless error_callback.present?
203
271
 
204
- error_callback.call(current_step, current_arg, error)
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
@@ -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
- define_attr_readers(opts)
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 = "#{opts.merge(argument: argument)}"
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
- attr_accessor :to_s
35
+ attr_writer :to_s
36
+ attr_accessor :meta_data, :status
33
37
 
34
- alias_method :name=, :to_s=
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(change_set)
40
- guard_args = [change_set, self]
40
+ set_mapped_options(acc)
41
+ guard_args = [acc, self]
41
42
 
42
- callbacks.run(:before, :step, change_set)
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, change_set) do
46
- set_result(change_set)
46
+ callbacks.around(:step, acc) do
47
+ set_result(acc)
47
48
  end
48
49
  end
49
50
 
50
- callbacks.run(:after, :step, change_set)
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(change_set)
62
+ def set_mapped_options(acc)
62
63
  mapper = options_mapper || default_options_mapper
63
- mapper_args = [change_set, self].take(mapper.arity)
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, :status, :error, :mapped_options, :execution_started_at, :execution_finished_at, :execution_duration
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(change_set)
81
- args = [self, change_set]
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 = args.take(callable.arity)
91
+ args = args.take(callable.arity) unless callable.arity.negative?
87
92
 
88
- if args.last.is_a?(Hash)
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 define_attr_readers(opts)
108
- opts.each do |key, value|
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
@@ -1,4 +1,4 @@
1
1
  module NxtPipeline
2
- VERSION = "1.0.0".freeze
2
+ VERSION = "2.0.0".freeze
3
3
  end
4
4
 
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: 1.0.0
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: 2021-01-20 00:00:00.000000000 Z
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