teckel 0.2.0 → 0.3.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: 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