teckel 0.8.0 → 0.10.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -7
  3. data/README.md +1 -2
  4. data/lib/teckel/chain/config.rb +20 -19
  5. data/lib/teckel/chain/result.rb +5 -9
  6. data/lib/teckel/chain/runner.rb +8 -6
  7. data/lib/teckel/chain.rb +16 -15
  8. data/lib/teckel/config.rb +15 -9
  9. data/lib/teckel/contracts.rb +3 -3
  10. data/lib/teckel/operation/config.rb +31 -28
  11. data/lib/teckel/operation/result.rb +12 -12
  12. data/lib/teckel/operation/runner.rb +1 -1
  13. data/lib/teckel/operation.rb +5 -5
  14. data/lib/teckel/result.rb +4 -4
  15. data/lib/teckel/version.rb +2 -1
  16. data/lib/teckel.rb +4 -1
  17. metadata +21 -56
  18. data/spec/chain/around_hook_spec.rb +0 -100
  19. data/spec/chain/default_settings_spec.rb +0 -39
  20. data/spec/chain/inheritance_spec.rb +0 -116
  21. data/spec/chain/none_input_spec.rb +0 -36
  22. data/spec/chain/results_spec.rb +0 -53
  23. data/spec/chain_spec.rb +0 -255
  24. data/spec/config_spec.rb +0 -26
  25. data/spec/doctest_helper.rb +0 -8
  26. data/spec/operation/config_spec.rb +0 -227
  27. data/spec/operation/contract_trace_spec.rb +0 -118
  28. data/spec/operation/default_settings_spec.rb +0 -120
  29. data/spec/operation/fail_on_input_spec.rb +0 -103
  30. data/spec/operation/inheritance_spec.rb +0 -94
  31. data/spec/operation/result_spec.rb +0 -49
  32. data/spec/operation/results_spec.rb +0 -117
  33. data/spec/operation_spec.rb +0 -531
  34. data/spec/rb27/pattern_matching_spec.rb +0 -193
  35. data/spec/result_spec.rb +0 -22
  36. data/spec/spec_helper.rb +0 -35
  37. data/spec/support/dry_base.rb +0 -8
  38. data/spec/support/fake_db.rb +0 -12
  39. data/spec/support/fake_models.rb +0 -20
  40. data/spec/teckel_spec.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 213ed6f9b3d25db8b7fc284ced55aa1a3a71b4ec54f10fd3ed3bf4e0a315dbd3
4
- data.tar.gz: d013f110763117b066987bfb6c085e8682db372856ea7ab27f4803858be96e3c
3
+ metadata.gz: 7d5501f2c908d365f035892a55463a35ca8af67f667f00c86d995570c604c2b9
4
+ data.tar.gz: 9100810bce08bffe1ea102d5613d8a2cca72d9a4b775247804c473bb0434bbba
5
5
  SHA512:
6
- metadata.gz: 82db9106f0da688411be738866692455653eb59298afa01ba24500975b0498bf9a164f9f59e2741a4ed9c0c252174bd86fa71dd4204338fe04424d961c21621b
7
- data.tar.gz: 6c693373c212e3b127dde042fdaba1ab7865d813cba17d0624a73a62e62589fc68bfe86ed93b9e7c32295deb6053411e4ea0cf57f0897b6f8e7e339ce60dd7be
6
+ metadata.gz: a7cdec32036460d09fcba3793eb94a33028e833a267dce8f4dedb2859b4104371f3d84d54e41e895f81c4c62a2ea5137776e7e378f6d98f6101e72cad19618c3
7
+ data.tar.gz: b5e1f99450292952b164bede8a3fd8d74073be39c9b6d39bd8cda33ab2c354cc5411ad1e10238b518240bf3d31d2cf61f5679c457b147ececbfdaf1fa9148798
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changes
2
2
 
3
+ ## 0.10.0
4
+
5
+ - `Teckel::Operation::Result` railway methods no longer capture block into variable
6
+ - Internal housekeeping
7
+ - Add ruby 4.0 to CI
8
+ - Allow bundler 4 in development
9
+
10
+ ## 0.9.0
11
+
12
+ - Internal housekeeping
13
+ - Add ruby 3.4 and 'head' to CI
14
+ - Ruby < 3 won't run mutation test
15
+ - moved to standard.rb for linting and formatting
16
+ - removed usage of `forwardable` in favor of explicit delegation methods
17
+ - renamed internal `Teckel::Config.for` to `get_or_set`
18
+ - renamed internal `Teckel::Operation::Config.get_set_constructor` to `get_or_set_constructor`
19
+ - fixed specs that test backtrace outputs on ruby 3.4
20
+ - Documentation now uses modern (Ruby 3.4) output syntax. (`{foo: 1}` is now printed as is, instead of `{:foo => 1}`
21
+
3
22
  ## 0.8.0
4
23
 
5
24
  - Add mutation testing (currently about 80% covered)
@@ -7,11 +26,13 @@
7
26
  When using the default result implementation, nothing changes for you.
8
27
  When you manually pass anything else into it, `succesful?` will return this value. The `failure` and `success` methods
9
28
  work on the "Truthy" value of `successful`
29
+
10
30
  ```ruby
11
31
  result = Result.new(some_value, 42)
12
32
  result.successful?
13
33
  # => 42
14
34
  ```
35
+
15
36
  - Change: `freeze`ing an `Operation` or `Chain` will also freeze their internal config (input, output, steps, etc.)
16
37
 
17
38
  Internal:
@@ -25,20 +46,20 @@ Internal:
25
46
  - Breaking: `Teckel::Chain` will not be required by default. require manually if needed `require "teckel/chain"` [GH-24]
26
47
  - Breaking: Internally, `Teckel::Operation::Runner` instead of `:success` and `:failure` now only uses `:halt` as it's throw-catch symbol. [GH-26]
27
48
  - Add: Using the default `Teckel::Operation::Runner`, `input_constructor` and `result_constructor` will be executed
28
- within the context of the operation instance. This allows for `input_constructor` to call `fail!` and `success!`
49
+ within the context of the operation instance. This allows for `input_constructor` to call `fail!` and `success!`
29
50
  without ever `call`ing the operation. [GH-26]
30
51
 
31
-
32
52
  ## 0.6.0
33
53
 
34
54
  - Breaking: Operations return values will be ignored. [GH-21]
35
- * You'll need to use `success!` or `failure!`
36
- * `success!` and `failure!` are now implemented on the `Runner`, which makes it easier to change their behavior (including the one above).
55
+ - You'll need to use `success!` or `failure!`
56
+ - `success!` and `failure!` are now implemented on the `Runner`, which makes it easier to change their behavior (including the one above).
37
57
 
38
58
  ## 0.5.0
39
59
 
40
60
  - Fix: calling chain with settings and no input [GH-14]
41
61
  - Add: Default settings for Operation and Chains [GH-17], [GH-18]
62
+
42
63
  ```ruby
43
64
  class MyOperation
44
65
  include Teckel::Operation
@@ -68,6 +89,7 @@ Internal:
68
89
  ```
69
90
 
70
91
  Internal:
92
+
71
93
  - Move operation and chain config dsl methods into own module [GH-15]
72
94
  - Code simplifications [GH-16]
73
95
 
@@ -77,6 +99,7 @@ Internal:
77
99
  - `#finalize!` no longer freezes the entire Operation or Chain class, only it's settings. [GH-13]
78
100
  - Add simple support for using Base classes. [GH-10]
79
101
  Removes global configuration `Teckel::Config.default_constructor`
102
+
80
103
  ```ruby
81
104
  class ApplicationOperation
82
105
  include Teckel::Operation
@@ -94,17 +117,21 @@ Internal:
94
117
  # you cannot call `finalize!` on partially declared Operations
95
118
  end
96
119
  ```
120
+
97
121
  - Add support for setting your own Result objects. [GH-9]
98
122
  - They should include and implement `Teckel::Result` which is needed by `Chain`.
99
123
  - `Chain::StepFailure` got replaced with `Chain::Result`.
100
124
  - the `Teckel::Operation::Results` module was removed. To let Operation use the default Result object, use the new helper `result!` instead.
101
125
  - Add "settings"/dependency injection to Operation and Chains. [GH-7]
126
+
102
127
  ```ruby
103
128
  MyOperation.with(logger: STDOUT).call(params)
104
129
 
105
130
  MyChain.with(some_step: { logger: STDOUT }).call(params)
106
131
  ```
132
+
107
133
  - [GH-5] Add support for ruby 2.7 pattern matching on Operation and Chain results. Both, array and hash notations are supported:
134
+
108
135
  ```ruby
109
136
  case MyOperation.call(params)
110
137
  in [false, value]
@@ -122,19 +149,20 @@ Internal:
122
149
  # handle success
123
150
  end
124
151
  ```
152
+
125
153
  - Fix setting a config twice to raise an error
126
154
 
127
155
  ## 0.3.0
128
156
 
129
157
  - `finalize!`'ing a Chain will also finalize all it's Operations
130
158
  - Changed attribute naming of `StepFailure`:
131
- + `.operation` will now give the operation class of the step - was `.step` before
132
- + `.step` will now give the name of the step (which Operation failed) - was `.step_name` before
159
+ - `.operation` will now give the operation class of the step - was `.step` before
160
+ - `.step` will now give the name of the step (which Operation failed) - was `.step_name` before
133
161
 
134
162
  ## 0.2.0
135
163
 
136
164
  - Around Hooks for Chains
137
- - `finalize!`
165
+ - `finalize!`
138
166
  - freezing Chains and Operations, to prevent further changes
139
167
  - Operations check their config and raise if any is missing
140
168
 
data/README.md CHANGED
@@ -4,8 +4,7 @@ Ruby service classes with enforced<sup name="footnote-1-source">[1](#footnote-1)
4
4
 
5
5
  [![Gem Version](https://img.shields.io/gem/v/teckel.svg)][gem]
6
6
  [![Build Status](https://github.com/fnordfish/teckel/actions/workflows/specs.yml/badge.svg)][ci]
7
- [![Maintainability](https://api.codeclimate.com/v1/badges/b3939aaec6271a567a57/maintainability)](https://codeclimate.com/github/fnordfish/teckel/maintainability)
8
- [![Test Coverage](https://api.codeclimate.com/v1/badges/b3939aaec6271a567a57/test_coverage)](https://codeclimate.com/github/fnordfish/teckel/test_coverage)
7
+ [![codecov](https://codecov.io/github/fnordfish/teckel/graph/badge.svg?token=8ZP6YNFRKX)](https://codecov.io/github/fnordfish/teckel)
9
8
  [![API Documentation Coverage](https://inch-ci.org/github/fnordfish/teckel.svg?branch=main)][inch]
10
9
 
11
10
  ## Installation
@@ -17,7 +17,7 @@ module Teckel
17
17
  #
18
18
  # @return [<Step>]
19
19
  def steps
20
- @config.for(:steps) { [] }
20
+ @config.get_or_set(:steps) { [] }
21
21
  end
22
22
 
23
23
  # Set or get the optional around hook.
@@ -26,9 +26,9 @@ module Teckel
26
26
  # chain ({Runner}) and the second argument the +input+ data. The hook also
27
27
  # needs to return the result.
28
28
  #
29
- # @param callable [Proc,{#call}] The hook to pass chain execution control to. (nil)
29
+ # @param callable [Proc,#call] The hook to pass chain execution control to. (nil)
30
30
  #
31
- # @return [Proc,{#call}] The configured hook
31
+ # @return [Proc,#call] The configured hook
32
32
  #
33
33
  # @example Around hook with block
34
34
  # OUTPUTS = []
@@ -62,7 +62,7 @@ module Teckel
62
62
  # OUTPUTS #=> ["before start", "after start"]
63
63
  # result.success #=> { some: "test" }
64
64
  def around(callable = nil, &block)
65
- @config.for(:around, callable || block)
65
+ @config.get_or_set(:around, callable || block)
66
66
  end
67
67
 
68
68
  # @!attribute [r] runner()
@@ -73,7 +73,7 @@ module Teckel
73
73
  # @param klass [Class] A class like the {Runner}
74
74
  # @!visibility protected
75
75
  def runner(klass = nil)
76
- @config.for(:runner, klass) { Runner }
76
+ @config.get_or_set(:runner, klass) { Runner }
77
77
  end
78
78
 
79
79
  # @overload result()
@@ -85,7 +85,7 @@ module Teckel
85
85
  # @param klass [Class] The +result+ class
86
86
  # @return [Class] The +result+ class configured
87
87
  def result(klass = nil)
88
- @config.for(:result, klass) { const_defined?(:Result, false) ? self::Result : Teckel::Chain::Result }
88
+ @config.get_or_set(:result, klass) { const_defined?(:Result, false) ? self::Result : Teckel::Chain::Result }
89
89
  end
90
90
 
91
91
  # @overload result_constructor()
@@ -143,7 +143,7 @@ module Teckel
143
143
  def result_constructor(sym_or_proc = nil)
144
144
  constructor = build_constructor(result, sym_or_proc) unless sym_or_proc.nil?
145
145
 
146
- @config.for(:result_constructor, constructor) {
146
+ @config.get_or_set(:result_constructor, constructor) {
147
147
  build_constructor(result, Teckel::DEFAULT_CONSTRUCTOR)
148
148
  } || raise(MissingConfigError, "Missing result_constructor config for #{self}")
149
149
  end
@@ -153,7 +153,7 @@ module Teckel
153
153
  #
154
154
  # Explicit call-time settings will *not* get merged with declared default setting.
155
155
  #
156
- # @param settings [Hash{String,Symbol => Object}] Set settings for a step by it's name
156
+ # @param settings [Hash{(String,Symbol) => Object}] Set settings for a step by it's name
157
157
  #
158
158
  # @example
159
159
  # class MyOperation
@@ -188,15 +188,18 @@ module Teckel
188
188
  # result = Chain.with(a: { other: "What" }).call
189
189
  # result.success #=> {say: nil, other: "What"}
190
190
  def default_settings!(settings) # :nodoc: The bang is for consistency with the Operation class
191
- @config.for(:default_settings, settings)
191
+ @config.get_or_set(:default_settings, settings)
192
192
  end
193
193
 
194
194
  # Getter for configured default settings
195
- # @return [nil|#call] The callable constructor
195
+ # @return [NilClass]
196
+ # @return [#call] The callable constructor
196
197
  def default_settings
197
- @config.for(:default_settings)
198
+ @config.get_or_set(:default_settings)
198
199
  end
199
200
 
201
+ # @!visibility private
202
+ # @return [Array<Symbol>]
200
203
  REQUIRED_CONFIGS = %i[around runner result result_constructor].freeze
201
204
 
202
205
  # @!visibility private
@@ -227,7 +230,7 @@ module Teckel
227
230
  # @return [self]
228
231
  # @!visibility public
229
232
  def dup
230
- dup_config(super())
233
+ dup_config(super()) # standard:disable Style/SuperArguments
231
234
  end
232
235
 
233
236
  # Produces a clone of this chain.
@@ -237,9 +240,9 @@ module Teckel
237
240
  # @!visibility public
238
241
  def clone
239
242
  if frozen?
240
- super()
243
+ super() # standard:disable Style/SuperArguments
241
244
  else
242
- dup_config(super())
245
+ dup_config(super()) # standard:disable Style/SuperArguments
243
246
  end
244
247
  end
245
248
 
@@ -250,7 +253,7 @@ module Teckel
250
253
  def freeze
251
254
  steps.freeze
252
255
  @config.freeze
253
- super()
256
+ super() # standard:disable Style/SuperArguments
254
257
  end
255
258
 
256
259
  # @!visibility private
@@ -263,9 +266,7 @@ module Teckel
263
266
  base.instance_variable_set(:@config, Teckel::Config.new)
264
267
  end
265
268
 
266
- private
267
-
268
- def dup_config(other_class)
269
+ private def dup_config(other_class)
269
270
  new_config = @config.dup
270
271
  new_config.replace(:steps) { steps.dup }
271
272
 
@@ -273,7 +274,7 @@ module Teckel
273
274
  other_class
274
275
  end
275
276
 
276
- def build_constructor(on, sym_or_proc)
277
+ private def build_constructor(on, sym_or_proc)
277
278
  case sym_or_proc
278
279
  when Proc
279
280
  sym_or_proc
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module Teckel
6
4
  module Chain
7
5
  class Result < Teckel::Operation::Result
8
- extend Forwardable
9
-
10
6
  # @param value [Object] The result value
11
7
  # @param success [Boolean] whether this is a successful result
12
8
  # @param step [Teckel::Chain::Step]
@@ -16,13 +12,13 @@ module Teckel
16
12
  end
17
13
 
18
14
  class << self
19
- alias :[] :new
15
+ alias_method :[], :new
20
16
  end
21
17
 
22
- # @!method step
23
- # Delegates to +step.name+
24
- # @return [String,Symbol] The step name of the failed operation.
25
- def_delegator :@step, :name, :step
18
+ # @return [String,Symbol] The step name of the failed operation.
19
+ def step
20
+ @step.name
21
+ end
26
22
 
27
23
  def deconstruct
28
24
  [successful?, @step.name, value]
@@ -7,9 +7,13 @@ module Teckel
7
7
  # @!visibility protected
8
8
  class Runner
9
9
  # @!visibility private
10
- UNDEFINED = Object.new
10
+ # @return [Object]
11
+ UNDEFINED = Teckel::UNDEFINED
11
12
 
12
13
  # @!visibility private
14
+ # @attr [Object] value the return value / result of the step execution
15
+ # @attr [Boolean] success whether the step has been executed successfully
16
+ # @attr [Teckel::Chain::Step] the step instance
13
17
  StepResult = Struct.new(:value, :success, :step)
14
18
 
15
19
  def initialize(chain, settings = UNDEFINED)
@@ -32,9 +36,7 @@ module Teckel
32
36
  settings.eql?(UNDEFINED) ? chain.steps : steps_with_settings
33
37
  end
34
38
 
35
- private
36
-
37
- def run(input)
39
+ private def run(input)
38
40
  steps.each_with_object(StepResult.new(input)) do |step, step_result|
39
41
  result = step.operation.call(step_result.value)
40
42
 
@@ -46,11 +48,11 @@ module Teckel
46
48
  end
47
49
  end
48
50
 
49
- def step_with_settings(step)
51
+ private def step_with_settings(step)
50
52
  settings.key?(step.name) ? step.with(settings[step.name]) : step
51
53
  end
52
54
 
53
- def steps_with_settings
55
+ private def steps_with_settings
54
56
  Enumerator.new do |yielder|
55
57
  chain.steps.each do |step|
56
58
  yielder << step_with_settings(step)
data/lib/teckel/chain.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'chain/config'
4
- require_relative 'chain/step'
5
- require_relative 'chain/result'
6
- require_relative 'chain/runner'
3
+ require_relative "chain/config"
4
+ require_relative "chain/step"
5
+ require_relative "chain/result"
6
+ require_relative "chain/runner"
7
7
 
8
8
  module Teckel
9
9
  # Railway style execution of multiple Operations.
@@ -32,10 +32,7 @@ module Teckel
32
32
  # List of all possible errors
33
33
  # @return [<Class>] List of all steps {Teckel::Operation.error}s
34
34
  def errors
35
- steps.each_with_object([]) do |step, m|
36
- err = step.operation.error
37
- m << err if err
38
- end
35
+ steps.map { |step| step.operation.error }
39
36
  end
40
37
 
41
38
  # The primary interface to call the chain with the given input.
@@ -45,13 +42,13 @@ module Teckel
45
42
  # @return [Teckel::Chain::Result] The result object wrapping
46
43
  # the result value, the success state and last executed step.
47
44
  def call(input = nil)
48
- default_settings = self.default_settings
45
+ default_settings = default_settings()
49
46
 
50
47
  runner =
51
48
  if default_settings
52
- self.runner.new(self, default_settings)
49
+ runner().new(self, default_settings)
53
50
  else
54
- self.runner.new(self)
51
+ runner().new(self)
55
52
  end
56
53
 
57
54
  if around
@@ -61,16 +58,20 @@ module Teckel
61
58
  end
62
59
  end
63
60
 
64
- # @param settings [Hash{String,Symbol => Object}] Set settings for a step by it's name
61
+ # Provide settings to the configured steps.
62
+ #
63
+ # @param settings [Hash{(String,Symbol) => Object}] Set settings for a step by it's name
64
+ # @return [#call] A callable, either a {Teckel::Chain::Runner} or,
65
+ # when configured with an around hook, a +Proc+
65
66
  def with(settings)
66
- runner = self.runner.new(self, settings)
67
+ runner = runner().new(self, settings)
67
68
  if around
68
- ->(input) { around.call(runner, input) }
69
+ around.curry[runner]
69
70
  else
70
71
  runner
71
72
  end
72
73
  end
73
- alias :set :with
74
+ alias_method :set, :with
74
75
  end
75
76
 
76
77
  def self.included(receiver)
data/lib/teckel/config.rb CHANGED
@@ -15,11 +15,16 @@ module Teckel
15
15
  # - sets (and returns) the blocks return value otherwise
16
16
  # - calling without +value+ and +block+ works like {Hash#[]}
17
17
  #
18
+ # @param key [Symbol,String] Name of the configuration
19
+ # @param value [Object] Value of the configuration
20
+ # @yield [key] If using in fetch mode and no value has been set
21
+ # @yieldparam key [Symbol,String] Name of the configuration
22
+ # @return [Object]
18
23
  # @raise [FrozenConfigError] When overwriting a key
19
24
  # @!visibility private
20
- def for(key, value = nil, &block)
25
+ def get_or_set(key, value = nil, &block)
21
26
  if value.nil?
22
- get_or_set(key, &block)
27
+ _get_or_set(key, &block)
23
28
  elsif @config.key?(key)
24
29
  raise FrozenConfigError, "Configuration #{key} is already set"
25
30
  else
@@ -28,6 +33,9 @@ module Teckel
28
33
  end
29
34
 
30
35
  # @!visibility private
36
+ # @param key [Symbol,String] Name of the configuration
37
+ # @yieldreturn [Object] The new setting
38
+ # @return [Object,nil] The new setting or +nil+ if not replaced
31
39
  def replace(key)
32
40
  @config[key] = yield if @config.key?(key)
33
41
  end
@@ -35,19 +43,17 @@ module Teckel
35
43
  # @!visibility private
36
44
  def freeze
37
45
  @config.freeze
38
- super()
46
+ super() # standard:disable Style/SuperArguments
39
47
  end
40
48
 
41
49
  # @!visibility private
42
50
  def dup
43
- super().tap do |copy|
44
- copy.instance_variable_set(:@config, @config.dup)
45
- end
51
+ copy = super() # standard:disable Style/SuperArguments
52
+ copy.instance_variable_set(:@config, @config.dup)
53
+ copy
46
54
  end
47
55
 
48
- private
49
-
50
- def get_or_set(key, &block)
56
+ private def _get_or_set(key, &block)
51
57
  if block
52
58
  @config[key] ||= @config.fetch(key, &block)
53
59
  else
@@ -5,14 +5,14 @@ module Teckel
5
5
  # Simple contract for enforcing data to be not set or +nil+
6
6
  module None
7
7
  class << self
8
- # Always return nil
9
- # @return nil
8
+ # Always return +nil+
9
+ # @return [NilClass]
10
10
  # @raise [ArgumentError] when called with any non-nil arguments
11
11
  def [](*args)
12
12
  raise ArgumentError, "None called with arguments" if args.any?(&:itself)
13
13
  end
14
14
 
15
- alias :new :[]
15
+ alias_method :new, :[]
16
16
  end
17
17
  end
18
18
  end