teckel 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/teckel/chain.rb +76 -36
- data/lib/teckel/config.rb +13 -0
- data/lib/teckel/operation.rb +16 -8
- data/lib/teckel/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 520b717d1e115614b1c93cfac56deac0e567bfc5d39e2ca6e93094f3c958fd9b
|
4
|
+
data.tar.gz: d5f8f5a5dd2a2dabebe1add49ab139e9c5d14fd5c1fd3b1ef92f78c99c604401
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb4021c0102a0d18e45df23a40669984462b5d469608f16cefcff68fefa38a088ae4851f51052ba5b3c3f5305dc6d0abfbf2a50a81ca240748cf226ea5b66366
|
7
|
+
data.tar.gz: 4298d3bff376cace7dd78a863fc60da2f7e4600f8032da19a3c7ecaee6168146fe41658af16e1811ad7de157cf15a37aa0bad42cdc19602de8eac1b2a2f49a83
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
- `finalize!`'ing a Chain will also finalize all it's Operations
|
6
|
+
- Changed attribute naming of `StepFailure`:
|
7
|
+
+ `.operation` will now give the operation class of the step - was `.step` before
|
8
|
+
+ `.step` will now give the name of the step (which Operation failed) - was `.step_name` before
|
9
|
+
|
3
10
|
## 0.2.0
|
4
11
|
|
5
12
|
- Around Hooks for Chains
|
data/lib/teckel/chain.rb
CHANGED
@@ -77,20 +77,20 @@ module Teckel
|
|
77
77
|
#
|
78
78
|
# result = MyChain.call(name: "Bob", age: 23)
|
79
79
|
# result.is_a?(Teckel::Result) #=> true
|
80
|
-
# result.success[:user].is_a?(User)
|
81
|
-
# result.success[:friend].is_a?(User)
|
80
|
+
# result.success[:user].is_a?(User) #=> true
|
81
|
+
# result.success[:friend].is_a?(User) #=> true
|
82
82
|
#
|
83
83
|
# AddFriend.fail_befriend = true
|
84
84
|
# failure_result = MyChain.call(name: "Bob", age: 23)
|
85
85
|
# failure_result.is_a?(Teckel::Chain::StepFailure) #=> true
|
86
86
|
#
|
87
87
|
# # additional step information
|
88
|
-
# failure_result.
|
89
|
-
# failure_result.
|
88
|
+
# failure_result.step #=> :befriend
|
89
|
+
# failure_result.operation #=> AddFriend
|
90
90
|
#
|
91
91
|
# # otherwise behaves just like a normal +Result+
|
92
|
-
# failure_result.failure?
|
93
|
-
# failure_result.failure
|
92
|
+
# failure_result.failure? #=> true
|
93
|
+
# failure_result.failure #=> {message: "Did not find a friend."}
|
94
94
|
#
|
95
95
|
# @example DB transaction around hook
|
96
96
|
# class CreateUser
|
@@ -163,33 +163,44 @@ module Teckel
|
|
163
163
|
# failure_result.is_a?(Teckel::Chain::StepFailure) #=> true
|
164
164
|
#
|
165
165
|
# # triggered DB rollback
|
166
|
-
# LOG
|
166
|
+
# LOG #=> [:before, :rollback]
|
167
167
|
#
|
168
168
|
# # additional step information
|
169
|
-
# failure_result.
|
170
|
-
# failure_result.
|
169
|
+
# failure_result.step #=> :befriend
|
170
|
+
# failure_result.operation #=> AddFriend
|
171
171
|
#
|
172
172
|
# # otherwise behaves just like a normal +Result+
|
173
|
-
# failure_result.failure?
|
174
|
-
# failure_result.failure
|
173
|
+
# failure_result.failure? #=> true
|
174
|
+
# failure_result.failure #=> {message: "Did not find a friend."}
|
175
175
|
module Chain
|
176
|
+
# Internal wrapper of a step definition
|
177
|
+
Step = Struct.new(:name, :operation) do
|
178
|
+
def finalize!
|
179
|
+
name.freeze
|
180
|
+
operation.finalize!
|
181
|
+
freeze
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
176
185
|
# Like {Teckel::Result Teckel::Result} but for failing Chains
|
177
186
|
#
|
178
187
|
# When a Chain fails, it stores the failed +Operation+ and it's name.
|
179
188
|
class StepFailure
|
180
189
|
extend Forwardable
|
181
190
|
|
182
|
-
def initialize(step,
|
183
|
-
@step, @
|
191
|
+
def initialize(step, result)
|
192
|
+
@step, @result = step, result
|
184
193
|
end
|
185
194
|
|
186
|
-
# @!
|
187
|
-
#
|
188
|
-
|
195
|
+
# @!method step
|
196
|
+
# Delegates to +step.name+
|
197
|
+
# @return [String,Symbol] The name of the failed operation.
|
198
|
+
def_delegator :@step, :name, :step
|
189
199
|
|
190
|
-
# @!
|
191
|
-
#
|
192
|
-
|
200
|
+
# @!method operation
|
201
|
+
# Delegates to +step.operation+
|
202
|
+
# @return [Teckel::Operation] The failed Operation class.
|
203
|
+
def_delegator :@step, :operation
|
193
204
|
|
194
205
|
# @!attribute result [R]
|
195
206
|
# @return [Teckel::Result] the failure Result
|
@@ -232,10 +243,10 @@ module Teckel
|
|
232
243
|
def call(input)
|
233
244
|
last_result = input
|
234
245
|
failed = nil
|
235
|
-
steps.each do |
|
236
|
-
last_result = step.call(last_result)
|
246
|
+
steps.each do |step|
|
247
|
+
last_result = step.operation.call(last_result)
|
237
248
|
if last_result.failure?
|
238
|
-
failed = StepFailure.new(step,
|
249
|
+
failed = StepFailure.new(step, last_result)
|
239
250
|
break
|
240
251
|
end
|
241
252
|
end
|
@@ -248,20 +259,20 @@ module Teckel
|
|
248
259
|
# The expected input for this chain
|
249
260
|
# @return [Class] The {Teckel::Operation.input} of the first step
|
250
261
|
def input
|
251
|
-
|
262
|
+
steps.first&.operation&.input
|
252
263
|
end
|
253
264
|
|
254
265
|
# The expected output for this chain
|
255
266
|
# @return [Class] The {Teckel::Operation.output} of the last step
|
256
267
|
def output
|
257
|
-
|
268
|
+
steps.last&.operation&.output
|
258
269
|
end
|
259
270
|
|
260
271
|
# List of all possible errors
|
261
272
|
# @return [<Class>] List of all steps {Teckel::Operation.error}s
|
262
273
|
def errors
|
263
|
-
|
264
|
-
err =
|
274
|
+
steps.each_with_object([]) do |step, m|
|
275
|
+
err = step.operation.error
|
265
276
|
m << err if err
|
266
277
|
end
|
267
278
|
end
|
@@ -273,7 +284,14 @@ module Teckel
|
|
273
284
|
# @param operation [Operation::Results] The operation to call.
|
274
285
|
# Must return a {Teckel::Result} object.
|
275
286
|
def step(name, operation)
|
276
|
-
|
287
|
+
steps << Step.new(name, operation)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Get the list of defined steps
|
291
|
+
#
|
292
|
+
# @return [<Step>]
|
293
|
+
def steps
|
294
|
+
@config.for(:steps) { [] }
|
277
295
|
end
|
278
296
|
|
279
297
|
# Set or get the optional around hook.
|
@@ -339,7 +357,7 @@ module Teckel
|
|
339
357
|
# either the success or failure value. Note that the {StepFailure} behaves
|
340
358
|
# just like a {Teckel::Result} with added information about which step failed.
|
341
359
|
def call(input)
|
342
|
-
runner = self.runner.new(
|
360
|
+
runner = self.runner.new(steps)
|
343
361
|
if around
|
344
362
|
around.call(runner, input)
|
345
363
|
else
|
@@ -347,33 +365,56 @@ module Teckel
|
|
347
365
|
end
|
348
366
|
end
|
349
367
|
|
368
|
+
# @!visibility private
|
369
|
+
# @return [void]
|
370
|
+
def define!
|
371
|
+
raise MissingConfigError, "Cannot define Chain with no steps" if steps.empty?
|
372
|
+
|
373
|
+
%i[around runner].each { |e| public_send(e) }
|
374
|
+
steps.each(&:finalize!)
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
|
350
378
|
# Disallow any further changes to this Chain.
|
379
|
+
# @note This also calls +finalize!+ on all Operations defined as steps.
|
351
380
|
#
|
352
381
|
# @return [self] Frozen self
|
353
382
|
# @!visibility public
|
354
383
|
def finalize!
|
355
|
-
|
356
|
-
|
384
|
+
define!
|
385
|
+
steps.freeze
|
357
386
|
@config.freeze
|
358
|
-
|
387
|
+
freeze
|
359
388
|
end
|
360
389
|
|
390
|
+
# Produces a shallow copy of this chain.
|
391
|
+
# It's {around}, {runner} and {steps} will get +dup+'ed
|
392
|
+
#
|
393
|
+
# @return [self]
|
361
394
|
# @!visibility public
|
362
395
|
def dup
|
363
396
|
super.tap do |copy|
|
364
|
-
|
365
|
-
|
397
|
+
new_config = @config.dup
|
398
|
+
new_config.replace(:steps) { steps.dup }
|
399
|
+
|
400
|
+
copy.instance_variable_set(:@config, new_config)
|
366
401
|
end
|
367
402
|
end
|
368
403
|
|
404
|
+
# Produces a clone of this chain.
|
405
|
+
# It's {around}, {runner} and {steps} will get +dup+'ed
|
406
|
+
#
|
407
|
+
# @return [self]
|
369
408
|
# @!visibility public
|
370
409
|
def clone
|
371
410
|
if frozen?
|
372
411
|
super
|
373
412
|
else
|
374
413
|
super.tap do |copy|
|
375
|
-
|
376
|
-
|
414
|
+
new_config = @config.dup
|
415
|
+
new_config.replace(:steps) { steps.dup }
|
416
|
+
|
417
|
+
copy.instance_variable_set(:@config, new_config)
|
377
418
|
end
|
378
419
|
end
|
379
420
|
end
|
@@ -383,7 +424,6 @@ module Teckel
|
|
383
424
|
receiver.extend ClassMethods
|
384
425
|
|
385
426
|
receiver.class_eval do
|
386
|
-
@steps = []
|
387
427
|
@config = Config.new
|
388
428
|
end
|
389
429
|
end
|
data/lib/teckel/config.rb
CHANGED
@@ -30,6 +30,14 @@ module Teckel
|
|
30
30
|
@config = {}
|
31
31
|
end
|
32
32
|
|
33
|
+
# Allow getting or setting a value, with some weird rules:
|
34
|
+
# - The +value+ might not be +nil+
|
35
|
+
# - Setting via +value+ is allowed only once. Successive calls will raise a {FrozenConfigError}
|
36
|
+
# - Setting via +block+ works almost like {Hash#fetch}:
|
37
|
+
# - returns the existing value if key is present
|
38
|
+
# - sets (and returns) the blocks return value otherwise
|
39
|
+
# - calling without +value+ and +block+ works like {Hash#[]}
|
40
|
+
#
|
33
41
|
# @!visibility private
|
34
42
|
def for(key, value = nil, &block)
|
35
43
|
if value.nil?
|
@@ -45,6 +53,11 @@ module Teckel
|
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
56
|
+
# @!visibility private
|
57
|
+
def replace(key)
|
58
|
+
@config[key] = yield if @config.key?(key)
|
59
|
+
end
|
60
|
+
|
48
61
|
# @!visibility private
|
49
62
|
def freeze
|
50
63
|
@config.freeze
|
data/lib/teckel/operation.rb
CHANGED
@@ -283,7 +283,8 @@ module Teckel
|
|
283
283
|
end
|
284
284
|
|
285
285
|
# Convenience method for setting {#input}, {#output} or {#error} to the {None} value.
|
286
|
-
# @return [None
|
286
|
+
# @return [Object] The {Teckel::None} class.
|
287
|
+
#
|
287
288
|
# @example Enforcing nil input, output or error
|
288
289
|
# class MyOperation
|
289
290
|
# include Teckel::Operation
|
@@ -333,7 +334,7 @@ module Teckel
|
|
333
334
|
end
|
334
335
|
|
335
336
|
# @!visibility private
|
336
|
-
# @return [
|
337
|
+
# @return [void]
|
337
338
|
def define!
|
338
339
|
%i[input input_constructor output output_constructor error error_constructor runner].each { |e|
|
339
340
|
public_send(e)
|
@@ -349,11 +350,13 @@ module Teckel
|
|
349
350
|
# @!visibility public
|
350
351
|
def finalize!
|
351
352
|
define!
|
352
|
-
freeze
|
353
353
|
@config.freeze
|
354
|
-
|
354
|
+
freeze
|
355
355
|
end
|
356
356
|
|
357
|
+
# Produces a shallow copy of this operation and all it's configuration.
|
358
|
+
#
|
359
|
+
# @return [self]
|
357
360
|
# @!visibility public
|
358
361
|
def dup
|
359
362
|
super.tap do |copy|
|
@@ -361,6 +364,9 @@ module Teckel
|
|
361
364
|
end
|
362
365
|
end
|
363
366
|
|
367
|
+
# Produces a clone of this operation and all it's configuration
|
368
|
+
#
|
369
|
+
# @return [self]
|
364
370
|
# @!visibility public
|
365
371
|
def clone
|
366
372
|
if frozen?
|
@@ -384,13 +390,17 @@ module Teckel
|
|
384
390
|
end
|
385
391
|
|
386
392
|
module InstanceMethods
|
387
|
-
# Halt any further execution with a
|
393
|
+
# Halt any further execution with a output value
|
394
|
+
#
|
395
|
+
# @return a thing matching your {Operation::ClassMethods#output Operation#output} definition
|
388
396
|
# @!visibility protected
|
389
397
|
def success!(*args)
|
390
398
|
throw :success, args
|
391
399
|
end
|
392
400
|
|
393
|
-
# Halt any further execution with an
|
401
|
+
# Halt any further execution with an error value
|
402
|
+
#
|
403
|
+
# @return a thing matching your {Operation::ClassMethods#error Operation#error} definition
|
394
404
|
# @!visibility protected
|
395
405
|
def fail!(*args)
|
396
406
|
throw :failure, args
|
@@ -404,8 +414,6 @@ module Teckel
|
|
404
414
|
receiver.class_eval do
|
405
415
|
@config = Config.new
|
406
416
|
|
407
|
-
@runner = Runner
|
408
|
-
|
409
417
|
protected :success!, :fail!
|
410
418
|
end
|
411
419
|
end
|
data/lib/teckel/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teckel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Schulze
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
135
|
- !ruby/object:Gem::Version
|
136
136
|
version: '0'
|
137
137
|
requirements: []
|
138
|
-
rubygems_version: 3.
|
138
|
+
rubygems_version: 3.0.3
|
139
139
|
signing_key:
|
140
140
|
specification_version: 4
|
141
141
|
summary: Operations with enforced in/out/err data structures
|