light-service 0.20.0 → 0.21.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: 4063f9a4d12da971cb46bea573e9961eb88cbdfd10e4e5fd9723a0d865556228
4
- data.tar.gz: a0cfdeea54d3410fe65766fc2565f99beecdce130ed57cde52783481c5db5bb7
3
+ metadata.gz: 8aa95d9b98d8f7f1337072fa420f5a964cd497831e9ab40518719cc02ff86cdb
4
+ data.tar.gz: 804c6c26860cb0f9f6c31eede5894d492023753dc61950cde52db9049e6002f6
5
5
  SHA512:
6
- metadata.gz: 16838fb136fad01357dd866f5cfff56070e7141a6a1bb034a72f86a808f1b58baa39f1c565f9c8b32abea7b398381630bc2b63baebcb537718e645aec3ba04b3
7
- data.tar.gz: d86bc97f33127bc07cdee6962edb9e8fd1ad363e01fe0feff3c719ce49c681c6ead6384274960bb56617ffbd1dfaefc20002ad8dc8482051d97b77eb0fcfcb09
6
+ metadata.gz: d0e4928cde90d7ceaea837220c8cc1c1aa943f5264999e5585a250dbcac13f69bbc432b988ba88ed00eb9829957f3b794ae8656347b1e344b6830a151438a544
7
+ data.tar.gz: ab9aa8e27f06fd90cae15e3ec11e6fbe156a9cef17754a20425dbfedf258070095ed51a398940043e912a959121370749243e178fad518ef743fff45fcb426ae
@@ -12,7 +12,7 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  os: [ubuntu-latest, macos-latest]
15
- ruby: ['3.1', '3.2', '3.3']
15
+ ruby: ['3.2', '3.3', '3.4', '4.0']
16
16
  runs-on: ${{ matrix.os }}
17
17
  continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
18
18
  env:
data/.rubocop.yml CHANGED
@@ -62,7 +62,7 @@ Lint/EmptyClass:
62
62
  - 'spec/**/*.rb'
63
63
 
64
64
  # Defaults after the Rubocop upgrade
65
- Gemspec/DateAssignment: # new in 1.10
65
+ Gemspec/DeprecatedAttributeAssignment: # new in 1.10
66
66
  Enabled: true
67
67
  Layout/LineEndStringConcatenationIndentation: # new in 1.18
68
68
  Enabled: true
data/README.md CHANGED
@@ -5,7 +5,6 @@
5
5
  [![Codecov](https://codecov.io/gh/adomokos/light-service/branch/main/graph/badge.svg)](https://codecov.io/gh/adomokos/light-service)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
7
7
  [![Download Count](https://img.shields.io/badge/download%3A-~5%20million-blue)](https://rubygems.org/gems/light-service)
8
- [![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
9
8
 
10
9
  LightService is a powerful and flexible service skeleton framework with an emphasis on simplicity
11
10
 
@@ -20,6 +19,7 @@ LightService is a powerful and flexible service skeleton framework with an empha
20
19
  - [Stopping the Series of Actions](#stopping-the-series-of-actions)
21
20
  - [Failing the Context](#failing-the-context)
22
21
  - [Skipping the rest of the actions](#skipping-the-rest-of-the-actions)
22
+ - [Skipping ALL the rest of the actions](#skipping-all-the-rest-of-the-actions)
23
23
  - [Benchmarking Actions with Around Advice](#benchmarking-actions-with-around-advice)
24
24
  - [Before and After Action Hooks](#before-and-after-action-hooks)
25
25
  - [Expects and Promises](#expects-and-promises)
@@ -397,6 +397,24 @@ end
397
397
 
398
398
  In the example above the organizer called 4 actions. The first 2 actions got executed successfully. The 3rd decided to skip the rest, the 4th action was not invoked. The context was successful.
399
399
 
400
+ ### Skipping ALL the rest of the actions
401
+ While `context.skip_remaining!` skips actions in the current scope (e.g. inside a `reduce_if` or `iterate` block), sometimes you want to halt the entire execution of the organizer and any parent organizers. You can do this by calling `context.skip_all_remaining!`.
402
+
403
+ Consider this example:
404
+
405
+ ```ruby
406
+ def self.actions
407
+ [
408
+ reduce_if(->(ctx) { ctx.should_execute_conditional_actions }, [
409
+ ConditionallyRunAction1, # calls ctx.skip_all_remaining!
410
+ ConditionallyRunAction2, # skipped
411
+ ]),
412
+ AlwaysRunAction, # also skipped with skip_all_remaining!
413
+ ]
414
+ end
415
+ ```
416
+
417
+ In this case, `AlwaysRunAction` will be skipped if `skip_all_remaining!` was called inside the `reduce_if` block. If `skip_remaining!` had been used instead, `AlwaysRunAction` would still execute because the "skip" state is normally reset when exiting a scoped block.
400
418
 
401
419
  ## Benchmarking Actions with Around Advice
402
420
  Benchmarking your action is needed when you profile the series of actions. You could add benchmarking logic to each and every action, however, that would blur the business logic you have in your actions.
@@ -438,6 +456,7 @@ Any object passed into `around_each` must respond to #call with two arguments: t
438
456
  ## Before and After Action Hooks
439
457
 
440
458
  In case you need to inject code right before and after the actions are executed, you can use the `before_actions` and `after_actions` hooks. It accepts one or multiple lambdas that the Action implementation will invoke. This addition to LightService is a great way to decouple instrumentation from business logic.
459
+ One important note: these actions only run on the first call to the organizer. Subsequent calls will skip both the before and after actions.
441
460
 
442
461
  Consider this code:
443
462
 
@@ -1026,20 +1045,23 @@ end
1026
1045
 
1027
1046
  This code is much easier to reason about, it's less noisy and it captures the goal of LightService well: simple, declarative code that's easy to understand.
1028
1047
 
1029
- The 9 different orchestrator constructs an organizer can have:
1048
+ The 10 different orchestrator constructs an organizer can have:
1030
1049
 
1031
1050
  1. `reduce_until`
1032
- 2. `reduce_if`
1033
- 3. `reduce_if_else`
1034
- 4. `reduce_case`
1035
- 5. `iterate`
1036
- 6. `execute`
1037
- 7. `with_callback`
1038
- 8. `add_to_context`
1039
- 9. `add_aliases`
1051
+ 2. `reduce_while`
1052
+ 3. `reduce_if`
1053
+ 4. `reduce_if_else`
1054
+ 5. `reduce_case`
1055
+ 6. `iterate`
1056
+ 7. `execute`
1057
+ 8. `with_callback`
1058
+ 9. `add_to_context`
1059
+ 10. `add_aliases`
1040
1060
 
1041
1061
  `reduce_until` behaves like a while loop in imperative languages, it iterates until the provided predicate in the lambda evaluates to true. Take a look at [this acceptance test](spec/acceptance/organizer/reduce_until_spec.rb) to see how it's used.
1042
1062
 
1063
+ `reduce_while` checks the provided predicate before each individual action in its step list. If the predicate evaluates to false, the remaining actions in the block are skipped. Unlike `reduce_until`, it is not a loop — it makes a single pass through the steps with a per-action condition gate. [This acceptance test](spec/acceptance/organizer/reduce_while_spec.rb) describes this functionality.
1064
+
1043
1065
  `reduce_if` will reduce the included organizers and/or actions if the predicate in the lambda evaluates to true. [This acceptance test](spec/acceptance/organizer/reduce_if_spec.rb) describes this functionality.
1044
1066
 
1045
1067
  `reduce_if_else` takes three arguments, a condition lambda, a first set of "if true" steps, and a second set of "if false" steps. If the lambda evaluates to true, the "if true" steps are executed, otherwise the "else steps" are executed. [This acceptance test](spec/acceptance/organizer/reduce_if_else_spec.rb) describes this functionality.
data/RELEASES.md CHANGED
@@ -1,5 +1,11 @@
1
1
  A brief list of new features and changes introduced with the specified version.
2
2
 
3
+ ### 0.21.0
4
+ * [Add skip_all_remaining!](https://github.com/adomokos/light-service/pull/279)
5
+ * [Fix skip_all_remaining! message preservation](https://github.com/adomokos/light-service/pull/280)
6
+ * [Fix infinite recursion in LightService::Deprecation.warn](https://github.com/adomokos/light-service/pull/283)
7
+ * [Add reduce_while orchestrator construct](https://github.com/adomokos/light-service/pull/284)
8
+
3
9
  ### 0.20.0
4
10
  * [Add back ActiveSupport](https://github.com/adomokos/light-service/pull/259)
5
11
  * [Fix argument errors of LightService::LocalizationAdapter](https://github.com/adomokos/light-service/pull/263)
@@ -18,6 +18,7 @@ module LightService
18
18
  @message = message
19
19
  @error_code = error_code
20
20
  @skip_remaining = false
21
+ @skip_all_remaining = false
21
22
 
22
23
  context.to_hash.each { |k, v| self[k] = v }
23
24
  end
@@ -51,6 +52,10 @@ module LightService
51
52
  @skip_remaining
52
53
  end
53
54
 
55
+ def skip_all_remaining?
56
+ @skip_all_remaining
57
+ end
58
+
54
59
  def reset_skip_remaining!
55
60
  @message = nil
56
61
  @skip_remaining = false
@@ -111,8 +116,13 @@ module LightService
111
116
  @skip_remaining = true
112
117
  end
113
118
 
119
+ def skip_all_remaining!(message = nil)
120
+ @message = message
121
+ @skip_all_remaining = true
122
+ end
123
+
114
124
  def stop_processing?
115
- failure? || skip_remaining?
125
+ failure? || skip_remaining? || skip_all_remaining?
116
126
  end
117
127
 
118
128
  def define_accessor_methods_for_keys(keys)
@@ -153,7 +163,8 @@ module LightService
153
163
 
154
164
  def inspect
155
165
  "#{self.class}(#{self}, success: #{success?}, message: #{check_nil(message)}, error_code: " \
156
- "#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, aliases: #{@aliases})"
166
+ "#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, " \
167
+ "skip_all_remaining: #{@skip_all_remaining}, aliases: #{@aliases})"
157
168
  end
158
169
 
159
170
  private
@@ -1,7 +1,6 @@
1
1
  module LightService
2
2
  module Deprecation
3
3
  class << self
4
- # :nocov:
5
4
  # Basic implementation of a deprecation warning
6
5
  def warn(message, callstack = caller)
7
6
  # Construct the warning message
@@ -9,11 +8,10 @@ module LightService
9
8
  warning_message += "Called from: #{callstack.first}\n" unless callstack.empty?
10
9
 
11
10
  # Output the warning message to stderr or a log file
12
- warn warning_message
11
+ Kernel.warn warning_message
13
12
 
14
13
  # Additional logging or actions can be added here
15
14
  end
16
- # :nocov:
17
15
  end
18
16
  end
19
17
  end
@@ -10,6 +10,8 @@ module LightService
10
10
  collection = ctx[collection_key]
11
11
  item_key = collection_key.to_s.singularize.to_sym
12
12
  collection.each do |item|
13
+ break if ctx.stop_processing?
14
+
13
15
  ctx[item_key] = item
14
16
  ctx = scoped_reduce(organizer, ctx, steps)
15
17
  end
@@ -9,7 +9,7 @@ module LightService
9
9
 
10
10
  loop do
11
11
  ctx = scoped_reduce(organizer, ctx, steps)
12
- break if condition_block.call(ctx) || ctx.failure?
12
+ break if condition_block.call(ctx) || ctx.stop_processing?
13
13
  end
14
14
 
15
15
  ctx
@@ -0,0 +1,29 @@
1
+ module LightService
2
+ module Organizer
3
+ class ReduceWhile
4
+ def self.run(organizer, condition_block, steps)
5
+ lambda do |ctx|
6
+ return ctx if ctx.stop_processing?
7
+
8
+ reset_skip(ctx)
9
+
10
+ Array(steps).each do |step|
11
+ break unless condition_block.call(ctx)
12
+
13
+ ctx = organizer.with(ctx).reduce([step])
14
+ break if ctx.stop_processing?
15
+ end
16
+
17
+ reset_skip(ctx)
18
+
19
+ ctx
20
+ end
21
+ end
22
+
23
+ def self.reset_skip(ctx)
24
+ ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
25
+ end
26
+ private_class_method :reset_skip
27
+ end
28
+ end
29
+ end
@@ -2,9 +2,9 @@ module LightService
2
2
  module Organizer
3
3
  module ScopedReducable
4
4
  def scoped_reduce(organizer, ctx, steps)
5
- ctx.reset_skip_remaining! unless ctx.failure?
5
+ ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
6
6
  ctx = organizer.with(ctx).reduce([steps])
7
- ctx.reset_skip_remaining! unless ctx.failure?
7
+ ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
8
8
 
9
9
  ctx
10
10
  end
@@ -91,7 +91,8 @@ module LightService
91
91
  end
92
92
 
93
93
  def skip_remaining?(context)
94
- context.respond_to?(:skip_remaining?) && context.skip_remaining?
94
+ (context.respond_to?(:skip_remaining?) && context.skip_remaining?) ||
95
+ (context.respond_to?(:skip_all_remaining?) && context.skip_all_remaining?)
95
96
  end
96
97
 
97
98
  def write_skip_remaining_log(context, action)
@@ -47,6 +47,10 @@ module LightService
47
47
  ReduceUntil.run(self, condition_block, steps)
48
48
  end
49
49
 
50
+ def reduce_while(condition_block, steps)
51
+ ReduceWhile.run(self, condition_block, steps)
52
+ end
53
+
50
54
  def reduce_case(**args)
51
55
  ReduceCase.run(self, **args)
52
56
  end
@@ -55,8 +59,8 @@ module LightService
55
59
  Iterate.run(self, collection_key, steps)
56
60
  end
57
61
 
58
- def execute(code_block)
59
- Execute.run(code_block)
62
+ def execute(code_block = nil, &block)
63
+ Execute.run(code_block || block)
60
64
  end
61
65
 
62
66
  def with_callback(action, steps)
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.20.0".freeze
2
+ VERSION = "0.21.0".freeze
3
3
  end
data/lib/light-service.rb CHANGED
@@ -18,6 +18,7 @@ require 'light-service/organizer/with_reducer_factory'
18
18
  require 'light-service/organizer/reduce_if'
19
19
  require 'light-service/organizer/reduce_if_else'
20
20
  require 'light-service/organizer/reduce_until'
21
+ require 'light-service/organizer/reduce_while'
21
22
  require 'light-service/organizer/reduce_case'
22
23
  require 'light-service/organizer/iterate'
23
24
  require 'light-service/organizer/execute'
@@ -19,12 +19,14 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency("activesupport", ">= 5.0", "< 9.0")
21
21
 
22
- gem.add_development_dependency("generator_spec", "~> 0.9.4")
23
- gem.add_development_dependency("test-unit", "~> 3.0") # Needed for generator specs.
24
- gem.add_development_dependency("rspec", "~> 3.0")
25
- gem.add_development_dependency("simplecov", "~> 0.17")
26
- gem.add_development_dependency("simplecov-cobertura", "~> 2.1")
27
- gem.add_development_dependency("rubocop", "~> 1.26.0")
28
- gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
29
- gem.add_development_dependency("pry", "~> 0.14")
22
+ gem.add_development_dependency("generator_spec", "~> 0.10")
23
+ gem.add_development_dependency("test-unit", "~> 3.6") # Needed for generator specs.
24
+ gem.add_development_dependency("rspec", "~> 3.13")
25
+ gem.add_development_dependency("simplecov", "~> 0.22")
26
+ gem.add_development_dependency("simplecov-cobertura", "~> 3.1")
27
+ gem.add_development_dependency("rubocop", "~> 1.75")
28
+ gem.add_development_dependency("rubocop-performance", "~> 1.2")
29
+ gem.add_development_dependency("pry", "~> 0.15")
30
+ gem.add_development_dependency("ostruct", "~> 0.6")
31
+ gem.add_development_dependency("benchmark", "~> 0.3")
30
32
  end
@@ -63,21 +63,19 @@ RSpec.describe LightService::Organizer do
63
63
  reduce(actions)
64
64
  end
65
65
 
66
- # rubocop:disable Metrics/AbcSize
67
66
  def self.actions
68
67
  [
69
68
  reduce_if(
70
69
  ->(c) { !c.nil? },
71
70
  [
72
71
  execute(->(c) { c[:first_reduce_if] = true }),
73
- execute(->(c) { c.skip_remaining! }),
72
+ execute(&:skip_remaining!),
74
73
  execute(->(c) { c[:second_reduce_if] = true })
75
74
  ]
76
75
  ),
77
76
  execute(->(c) { c[:last_outside] = true })
78
77
  ]
79
78
  end
80
- # rubocop:enable Metrics/AbcSize
81
79
  end
82
80
 
83
81
  result = org.call
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestReduceWhile
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ reduce_while(->(ctx) { ctx[:number] < 3 }, [
15
+ TestDoubles::AddsOneAction,
16
+ TestDoubles::AddsTwoAction
17
+ ])
18
+ ]
19
+ end
20
+ end
21
+
22
+ let(:empty_context) { LightService::Context.make }
23
+
24
+ it 'reduces while the block evaluates to true' do
25
+ result = TestReduceWhile.call(:number => 0)
26
+
27
+ expect(result).to be_success
28
+ expect(result[:number]).to eq(3)
29
+ end
30
+
31
+ it 'checks the condition before each action' do
32
+ result = TestReduceWhile.call(:number => 2)
33
+
34
+ expect(result).to be_success
35
+ expect(result[:number]).to eq(3)
36
+ end
37
+
38
+ it 'does not execute any steps when the condition is false from the start' do
39
+ result = TestReduceWhile.call(:number => 5)
40
+
41
+ expect(result).to be_success
42
+ expect(result[:number]).to eq(5)
43
+ end
44
+
45
+ it 'does not execute on failed context' do
46
+ empty_context.fail!('Something bad happened')
47
+
48
+ result = TestReduceWhile.call(empty_context)
49
+ expect(result).to be_failure
50
+ end
51
+
52
+ it 'does not execute a skipped context' do
53
+ empty_context.skip_remaining!('No more needed')
54
+
55
+ result = TestReduceWhile.call(empty_context)
56
+ expect(result).to be_success
57
+ end
58
+
59
+ it "is expected to know its organizer when reducing while a condition" do
60
+ result = TestReduceWhile.call(:number => 0)
61
+
62
+ expect(result.organized_by).to eq TestReduceWhile
63
+ end
64
+
65
+ it 'skips actions within its own scope' do
66
+ org = Class.new do
67
+ extend LightService::Organizer
68
+
69
+ def self.call
70
+ reduce(actions)
71
+ end
72
+
73
+ def self.actions
74
+ [
75
+ reduce_while(
76
+ ->(c) { !c.nil? },
77
+ [
78
+ execute(->(c) { c[:first_reduce_while] = true }),
79
+ execute(&:skip_remaining!),
80
+ execute(->(c) { c[:second_reduce_while] = true })
81
+ ]
82
+ ),
83
+ execute(->(c) { c[:last_outside] = true })
84
+ ]
85
+ end
86
+ end
87
+
88
+ result = org.call
89
+
90
+ aggregate_failures do
91
+ expect(result[:first_reduce_while]).to be true
92
+ expect(result[:second_reduce_while]).to be_nil
93
+ expect(result[:last_outside]).to be true
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "skip_all_remaining!" do
4
+ context "with regular organizer" do
5
+ let(:organizer) do
6
+ Class.new do
7
+ extend LightService::Organizer
8
+
9
+ def self.call(ctx)
10
+ with(ctx).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ execute(->(c) { c[:first] = true }),
16
+ execute(lambda(&:skip_all_remaining!)),
17
+ execute(->(c) { c[:second] = true })
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ it "skips all remaining actions" do
24
+ result = organizer.call(LightService::Context.make)
25
+
26
+ expect(result[:first]).to be true
27
+ expect(result[:second]).to be_nil
28
+ end
29
+ end
30
+
31
+ context "with an organizer with a reducer" do
32
+ let(:organizer) do
33
+ Class.new do
34
+ extend LightService::Organizer
35
+
36
+ def self.call(ctx)
37
+ with(ctx).reduce(actions)
38
+ end
39
+
40
+ def self.actions
41
+ [
42
+ reduce_if(
43
+ ->(_) { true },
44
+ [
45
+ execute(->(c) { c[:first_inside] = true }),
46
+ execute(lambda(&:skip_all_remaining!)),
47
+ execute(->(c) { c[:second_inside] = true })
48
+ ]
49
+ ),
50
+ execute(->(c) { c[:outside] = true })
51
+ ]
52
+ end
53
+ end
54
+ end
55
+
56
+ it "skips all remaining actions inside and outside the reducer" do
57
+ result = organizer.call(LightService::Context.make)
58
+
59
+ expect(result[:first_inside]).to be true
60
+ expect(result[:second_inside]).to be_nil
61
+ expect(result[:outside]).to be_nil
62
+ end
63
+ end
64
+
65
+ context "with a message" do
66
+ let(:organizer) do
67
+ Class.new do
68
+ extend LightService::Organizer
69
+
70
+ def self.call(ctx)
71
+ with(ctx).reduce(actions)
72
+ end
73
+
74
+ def self.actions
75
+ [
76
+ reduce_if(
77
+ ->(_) { true },
78
+ [
79
+ execute(->(c) { c.skip_all_remaining!("Skipping with message") })
80
+ ]
81
+ ),
82
+ execute(->(c) { c[:outside] = true })
83
+ ]
84
+ end
85
+ end
86
+ end
87
+
88
+ it "preserves the message when exiting scoped reducers" do
89
+ result = organizer.call(LightService::Context.make)
90
+
91
+ expect(result.message).to eq("Skipping with message")
92
+ expect(result[:outside]).to be_nil
93
+ end
94
+ end
95
+
96
+ context "with an organizer with nested reducers" do
97
+ let(:organizer) do
98
+ Class.new do
99
+ extend LightService::Organizer
100
+
101
+ def self.call(ctx)
102
+ with(ctx).reduce(actions)
103
+ end
104
+
105
+ def self.actions # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
106
+ [
107
+ reduce_if(
108
+ ->(_) { true },
109
+ [
110
+ iterate(:items, [
111
+ execute(lambda { |c|
112
+ c[:executed_items] ||= []
113
+ c[:executed_items] << c[:item]
114
+ }),
115
+ execute(->(c) { c.skip_all_remaining! if c[:item] == 2 }),
116
+ execute(lambda { |c|
117
+ c[:skipped_in_iterate] ||= []
118
+ c[:skipped_in_iterate] << c[:item]
119
+ })
120
+ ]),
121
+ execute(->(c) { c[:after_iterate_inside_if] = true })
122
+ ]
123
+ ),
124
+ execute(->(c) { c[:outside] = true })
125
+ ]
126
+ end
127
+ end
128
+ end
129
+
130
+ it "skips all remaining actions across all nested scopes" do
131
+ result = organizer.call(LightService::Context.make(:items => [1, 2, 3]))
132
+
133
+ expect(result[:executed_items]).to eq([1, 2])
134
+ expect(result[:skipped_in_iterate]).to eq([1])
135
+ expect(result[:after_iterate_inside_if]).to be_nil
136
+ expect(result[:outside]).to be_nil
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "skip_remaining!" do
4
+ context "with regular organizer" do
5
+ let(:organizer) do
6
+ Class.new do
7
+ extend LightService::Organizer
8
+
9
+ def self.call(ctx)
10
+ with(ctx).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ execute(->(c) { c[:first] = true }),
16
+ execute(lambda(&:skip_remaining!)),
17
+ execute(->(c) { c[:second] = true })
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ it "skips the remaining actions in the organizer" do
24
+ result = organizer.call(LightService::Context.make)
25
+
26
+ expect(result[:first]).to be true
27
+ expect(result[:second]).to be_nil
28
+ end
29
+ end
30
+
31
+ context "with an organizer with a reducer" do
32
+ let(:organizer) do
33
+ Class.new do
34
+ extend LightService::Organizer
35
+
36
+ def self.call(ctx)
37
+ with(ctx).reduce(actions)
38
+ end
39
+
40
+ def self.actions # rubocop:disable Metrics/AbcSize
41
+ [
42
+ iterate(:items, [
43
+ execute(lambda { |c|
44
+ c[:executed_items] ||= []
45
+ c[:executed_items] << c[:item]
46
+ }),
47
+ execute(->(c) { c.skip_remaining! if c[:item] == 2 }),
48
+ execute(lambda { |c|
49
+ c[:skipped_actions] ||= []
50
+ c[:skipped_actions] << c[:item]
51
+ })
52
+ ]),
53
+ execute(->(c) { c[:outside_iterate] = true })
54
+ ]
55
+ end
56
+ end
57
+ end
58
+
59
+ it "only skips remaining actions for the current item in the reducer" do
60
+ result = organizer.call(LightService::Context.make(:items => [1, 2, 3]))
61
+
62
+ expect(result[:executed_items]).to eq([1, 2, 3])
63
+ expect(result[:skipped_actions]).to eq([1, 3])
64
+ expect(result[:outside_iterate]).to be true
65
+ end
66
+ end
67
+ end
68
+
@@ -13,7 +13,8 @@ RSpec.describe LightService::Context do
13
13
  describe '#inspect' do
14
14
  it 'inspects the hash with all the fields' do
15
15
  inspected_context =
16
- "LightService::Context({}, success: true, message: '', error_code: nil, skip_remaining: false, aliases: {})"
16
+ "LightService::Context({}, success: true, message: '', error_code: nil, skip_remaining: false, " \
17
+ "skip_all_remaining: false, aliases: {})"
17
18
 
18
19
  expect(context.inspect).to eq(inspected_context)
19
20
  end
@@ -23,7 +24,7 @@ RSpec.describe LightService::Context do
23
24
 
24
25
  inspected_context =
25
26
  "LightService::Context({}, success: false, message: 'There was an error', error_code: nil, " \
26
- "skip_remaining: false, aliases: {})"
27
+ "skip_remaining: false, skip_all_remaining: false, aliases: {})"
27
28
 
28
29
  expect(context.inspect).to eq(inspected_context)
29
30
  end
@@ -33,7 +34,7 @@ RSpec.describe LightService::Context do
33
34
 
34
35
  inspected_context =
35
36
  "LightService::Context({}, success: true, message: 'No need to process', error_code: nil, " \
36
- "skip_remaining: true, aliases: {})"
37
+ "skip_remaining: true, skip_all_remaining: false, aliases: {})"
37
38
 
38
39
  expect(context.inspect).to eq(inspected_context)
39
40
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe LightService::Deprecation do
4
+ describe '.warn' do
5
+ it 'outputs a deprecation warning to stderr' do
6
+ expect { described_class.warn('foo is deprecated') }
7
+ .to output(/DEPRECATION WARNING: foo is deprecated/).to_stderr
8
+ end
9
+
10
+ it 'includes the caller location' do
11
+ expect { described_class.warn('bar is deprecated') }
12
+ .to output(/Called from:/).to_stderr
13
+ end
14
+
15
+ it 'calls Kernel.warn instead of recursing into itself' do
16
+ expect(Kernel).to receive(:warn).with(/DEPRECATION WARNING: test message/)
17
+ described_class.warn('test message')
18
+ end
19
+ end
20
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,17 +3,15 @@ $LOAD_PATH << File.join(File.dirname(__FILE__))
3
3
 
4
4
  if ENV['RUN_COVERAGE_REPORT']
5
5
  require 'simplecov'
6
+ require 'simplecov-cobertura'
6
7
 
7
8
  SimpleCov.start do
8
9
  add_filter 'vendor/'
9
10
  add_filter %r{^/spec/}
11
+
12
+ formatter SimpleCov::Formatter::CoberturaFormatter
10
13
  end
11
14
  SimpleCov.minimum_coverage_by_file 90
12
-
13
- require 'simplecov-cobertura'
14
- SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
15
- # require 'codecov'
16
- # SimpleCov.formatter = SimpleCov::Formatter::Codecov
17
15
  end
18
16
 
19
17
  require 'light-service'
data/spec/test_doubles.rb CHANGED
@@ -407,6 +407,24 @@ module TestDoubles
407
407
  end
408
408
  end
409
409
 
410
+ class ReduceWhileOrganizer
411
+ extend LightService::Organizer
412
+
413
+ def self.call(ctx)
414
+ with(ctx).reduce(actions)
415
+ end
416
+
417
+ def self.actions
418
+ [
419
+ AddsOneAction,
420
+ reduce_while(->(ctx) { ctx.number < 7 }, [
421
+ AddsTwoAction,
422
+ AddsThreeAction
423
+ ])
424
+ ]
425
+ end
426
+ end
427
+
410
428
  class ReduceIfOrganizer
411
429
  extend LightService::Organizer
412
430
 
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'ContextFactory - used with ReduceWhileOrganizer' do
5
+ let(:organizer) { TestDoubles::ReduceWhileOrganizer }
6
+
7
+ context 'when called with truthy block' do
8
+ it 'creates a context up-to the action defined before the reduce_while' do
9
+ ctx =
10
+ LightService::Testing::ContextFactory
11
+ .make_from(organizer)
12
+ .for(TestDoubles::AddsTwoAction)
13
+ .with(:number => 1)
14
+
15
+ expect(ctx[:number]).to eq(2)
16
+ end
17
+
18
+ it 'creates a context up-to the second step of the reduce_while' do
19
+ ctx =
20
+ LightService::Testing::ContextFactory
21
+ .make_from(organizer)
22
+ .for(TestDoubles::AddsThreeAction)
23
+ .with(:number => 1)
24
+
25
+ expect(ctx.number).to eq(4)
26
+ end
27
+ end
28
+
29
+ context 'when called with falsey block' do
30
+ it 'creates a context without executing the reduce_while steps' do
31
+ ctx =
32
+ LightService::Testing::ContextFactory
33
+ .make_from(organizer)
34
+ .for(TestDoubles::AddsThreeAction)
35
+ .with(:number => 7)
36
+
37
+ expect(ctx[:number]).to eq(8)
38
+ end
39
+ end
40
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: light-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Attila Domokos
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -35,112 +35,140 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: 0.9.4
38
+ version: '0.10'
39
39
  type: :development
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - "~>"
44
44
  - !ruby/object:Gem::Version
45
- version: 0.9.4
45
+ version: '0.10'
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: test-unit
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
50
  - - "~>"
51
51
  - !ruby/object:Gem::Version
52
- version: '3.0'
52
+ version: '3.6'
53
53
  type: :development
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '3.0'
59
+ version: '3.6'
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: rspec
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '3.0'
66
+ version: '3.13'
67
67
  type: :development
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '3.0'
73
+ version: '3.13'
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: simplecov
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '0.17'
80
+ version: '0.22'
81
81
  type: :development
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '0.17'
87
+ version: '0.22'
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: simplecov-cobertura
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '2.1'
94
+ version: '3.1'
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: '2.1'
101
+ version: '3.1'
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: rubocop
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - "~>"
107
107
  - !ruby/object:Gem::Version
108
- version: 1.26.0
108
+ version: '1.75'
109
109
  type: :development
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
112
112
  requirements:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: 1.26.0
115
+ version: '1.75'
116
116
  - !ruby/object:Gem::Dependency
117
117
  name: rubocop-performance
118
118
  requirement: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: 1.2.0
122
+ version: '1.2'
123
123
  type: :development
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
126
126
  requirements:
127
127
  - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: 1.2.0
129
+ version: '1.2'
130
130
  - !ruby/object:Gem::Dependency
131
131
  name: pry
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '0.14'
136
+ version: '0.15'
137
137
  type: :development
138
138
  prerelease: false
139
139
  version_requirements: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: '0.14'
143
+ version: '0.15'
144
+ - !ruby/object:Gem::Dependency
145
+ name: ostruct
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '0.6'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '0.6'
158
+ - !ruby/object:Gem::Dependency
159
+ name: benchmark
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: '0.3'
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '0.3'
144
172
  description: A service skeleton with an emphasis on simplicity
145
173
  email:
146
174
  - adomokos@gmail.com
@@ -184,6 +212,7 @@ files:
184
212
  - lib/light-service/organizer/reduce_if.rb
185
213
  - lib/light-service/organizer/reduce_if_else.rb
186
214
  - lib/light-service/organizer/reduce_until.rb
215
+ - lib/light-service/organizer/reduce_while.rb
187
216
  - lib/light-service/organizer/scoped_reducable.rb
188
217
  - lib/light-service/organizer/verify_call_method_exists.rb
189
218
  - lib/light-service/organizer/with_callback.rb
@@ -226,9 +255,12 @@ files:
226
255
  - spec/acceptance/organizer/reduce_if_else_spec.rb
227
256
  - spec/acceptance/organizer/reduce_if_spec.rb
228
257
  - spec/acceptance/organizer/reduce_until_spec.rb
258
+ - spec/acceptance/organizer/reduce_while_spec.rb
229
259
  - spec/acceptance/organizer/with_callback_spec.rb
230
260
  - spec/acceptance/rollback_spec.rb
261
+ - spec/acceptance/skip_all_remaining_spec.rb
231
262
  - spec/acceptance/skip_all_warning_spec.rb
263
+ - spec/acceptance/skip_remaining_spec.rb
232
264
  - spec/acceptance/testing/context_factory_spec.rb
233
265
  - spec/action_expected_keys_spec.rb
234
266
  - spec/action_expects_and_promises_spec.rb
@@ -238,6 +270,7 @@ files:
238
270
  - spec/context/inspect_spec.rb
239
271
  - spec/context_spec.rb
240
272
  - spec/i18n_localization_adapter_spec.rb
273
+ - spec/lib/deprecation_warning_spec.rb
241
274
  - spec/lib/generators/action_generator_advanced_spec.rb
242
275
  - spec/lib/generators/action_generator_simple_spec.rb
243
276
  - spec/lib/generators/full_generator_test_blobs.rb
@@ -261,6 +294,7 @@ files:
261
294
  - spec/testing/context_factory/iterate_spec.rb
262
295
  - spec/testing/context_factory/reduce_if_spec.rb
263
296
  - spec/testing/context_factory/reduce_until_spec.rb
297
+ - spec/testing/context_factory/reduce_while_spec.rb
264
298
  - spec/testing/context_factory/with_callback_spec.rb
265
299
  - spec/testing/context_factory_spec.rb
266
300
  homepage: https://github.com/adomokos/light-service
@@ -281,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
281
315
  - !ruby/object:Gem::Version
282
316
  version: '0'
283
317
  requirements: []
284
- rubygems_version: 3.6.2
318
+ rubygems_version: 4.0.6
285
319
  specification_version: 4
286
320
  summary: A service skeleton with an emphasis on simplicity
287
321
  test_files:
@@ -313,9 +347,12 @@ test_files:
313
347
  - spec/acceptance/organizer/reduce_if_else_spec.rb
314
348
  - spec/acceptance/organizer/reduce_if_spec.rb
315
349
  - spec/acceptance/organizer/reduce_until_spec.rb
350
+ - spec/acceptance/organizer/reduce_while_spec.rb
316
351
  - spec/acceptance/organizer/with_callback_spec.rb
317
352
  - spec/acceptance/rollback_spec.rb
353
+ - spec/acceptance/skip_all_remaining_spec.rb
318
354
  - spec/acceptance/skip_all_warning_spec.rb
355
+ - spec/acceptance/skip_remaining_spec.rb
319
356
  - spec/acceptance/testing/context_factory_spec.rb
320
357
  - spec/action_expected_keys_spec.rb
321
358
  - spec/action_expects_and_promises_spec.rb
@@ -325,6 +362,7 @@ test_files:
325
362
  - spec/context/inspect_spec.rb
326
363
  - spec/context_spec.rb
327
364
  - spec/i18n_localization_adapter_spec.rb
365
+ - spec/lib/deprecation_warning_spec.rb
328
366
  - spec/lib/generators/action_generator_advanced_spec.rb
329
367
  - spec/lib/generators/action_generator_simple_spec.rb
330
368
  - spec/lib/generators/full_generator_test_blobs.rb
@@ -348,5 +386,6 @@ test_files:
348
386
  - spec/testing/context_factory/iterate_spec.rb
349
387
  - spec/testing/context_factory/reduce_if_spec.rb
350
388
  - spec/testing/context_factory/reduce_until_spec.rb
389
+ - spec/testing/context_factory/reduce_while_spec.rb
351
390
  - spec/testing/context_factory/with_callback_spec.rb
352
391
  - spec/testing/context_factory_spec.rb