nxt_pipeline 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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