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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9fbb7828be6e84c5d609241e778e39e8509a4cede9f8960f097b31ef0d2473b
4
- data.tar.gz: b646f5954b1fb331186f52cf2a6ccd8521004bb26e59a09ec6b6d4d8575affa2
3
+ metadata.gz: 520b717d1e115614b1c93cfac56deac0e567bfc5d39e2ca6e93094f3c958fd9b
4
+ data.tar.gz: d5f8f5a5dd2a2dabebe1add49ab139e9c5d14fd5c1fd3b1ef92f78c99c604401
5
5
  SHA512:
6
- metadata.gz: 454b1869e2b2a8bf540f203fccf9962d628c236c2ced1411ded69ca2ccb1e0c361965c74ad73dea6c709b97b7757016c24487f983924111b03f4f0c1c2d0287e
7
- data.tar.gz: fd5f0e9e4e147238423454ef7a801139fd6f0ef3b1a7c2198285b7c471f1a26fb64c879647a9c041edb8129e4279a00e52572038959e9b8bd473668772dd78e2
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) #=> true
81
- # result.success[:friend].is_a?(User) #=> true
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.step_name #=> :befriend
89
- # failure_result.step #=> AddFriend
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? #=> true
93
- # failure_result.failure #=> {message: "Did not find a friend."}
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 #=> [:before, :rollback]
166
+ # LOG #=> [:before, :rollback]
167
167
  #
168
168
  # # additional step information
169
- # failure_result.step_name #=> :befriend
170
- # failure_result.step #=> AddFriend
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? #=> true
174
- # failure_result.failure #=> {message: "Did not find a friend."}
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, step_name, result)
183
- @step, @step_name, @result = step, step_name, result
191
+ def initialize(step, result)
192
+ @step, @result = step, result
184
193
  end
185
194
 
186
- # @!attribute step [R]
187
- # @return [Teckel::Operation] the failed Operation
188
- attr_reader :step
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
- # @!attribute step_name [R]
191
- # @return [String] the step name of the failed Operation
192
- attr_reader :step_name
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 |(name, step)|
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, name, last_result)
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
- @steps.first&.last&.input
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
- @steps.last&.last&.output
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
- @steps.each_with_object([]) do |e, m|
264
- err = e.last&.error
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
- @steps << [name, operation]
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(@steps)
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
- freeze
356
- @steps.freeze
384
+ define!
385
+ steps.freeze
357
386
  @config.freeze
358
- self
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
- copy.instance_variable_set(:@steps, @steps.dup)
365
- copy.instance_variable_set(:@config, @config.dup)
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
- copy.instance_variable_set(:@steps, @steps.dup)
376
- copy.instance_variable_set(:@config, @config.dup)
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
@@ -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 [nil]
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
- self
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 +output+ value
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 +error+ value
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teckel
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.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-14 00:00:00.000000000 Z
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.1.2
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