functional-light-service 6.0.0 → 6.2.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/.gitignore +2 -0
- data/CHANGELOG.md +26 -0
- data/README.md +130 -11
- data/functional-light-service.gemspec +3 -1
- data/lib/functional-light-service/action.rb +48 -0
- data/lib/functional-light-service/configuration.rb +12 -2
- data/lib/functional-light-service/context/key_verifier.rb +1 -1
- data/lib/functional-light-service/context.rb +15 -2
- data/lib/functional-light-service/errors.rb +1 -0
- data/lib/functional-light-service/functional/sequencer.rb +144 -0
- data/lib/functional-light-service/i18n/localization_adapter.rb +50 -0
- data/lib/functional-light-service/localization_adapter.rb +19 -28
- data/lib/functional-light-service/localization_map.rb +9 -0
- data/lib/functional-light-service/organizer/reduce_case.rb +50 -0
- data/lib/functional-light-service/organizer/reduce_if_else.rb +23 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +1 -1
- data/lib/functional-light-service/organizer/reduce_while.rb +31 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +2 -2
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +2 -1
- data/lib/functional-light-service/organizer.rb +18 -3
- data/lib/functional-light-service/version.rb +1 -1
- data/lib/functional-light-service.rb +6 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +24 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +22 -0
- data/spec/acceptance/organizer/execute_spec.rb +21 -0
- data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
- data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
- data/spec/action_optional_expected_keys_spec.rb +107 -0
- data/spec/context/inspect_spec.rb +13 -3
- data/spec/context_spec.rb +12 -0
- data/spec/i18n_localization_adapter_spec.rb +83 -0
- data/spec/lib/deterministic/sequencer_spec.rb +506 -0
- data/spec/localization_adapter_spec.rb +66 -83
- data/spec/spec_helper.rb +3 -0
- data/spec/test_doubles.rb +28 -0
- metadata +27 -14
|
@@ -1,48 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'dry/inflector'
|
|
4
4
|
|
|
5
5
|
module FunctionalLightService
|
|
6
|
+
# Adapter built-in basato su LocalizationMap: nessuna dipendenza da I18n.
|
|
7
|
+
# Viene selezionato da Configuration quando la costante ::I18n non esiste
|
|
6
8
|
class LocalizationAdapter
|
|
7
|
-
|
|
9
|
+
INFLECTOR = Dry::Inflector.new
|
|
10
|
+
|
|
11
|
+
def failure(message_or_key, action_class, options = {})
|
|
8
12
|
find_translated_message(message_or_key,
|
|
9
|
-
action_class,
|
|
10
|
-
|
|
11
|
-
:type => :failure)
|
|
13
|
+
INFLECTOR.underscore(action_class.to_s),
|
|
14
|
+
options.merge(:type => :failures))
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
def success(message_or_key, action_class,
|
|
17
|
+
def success(message_or_key, action_class, options = {})
|
|
15
18
|
find_translated_message(message_or_key,
|
|
16
|
-
action_class,
|
|
17
|
-
|
|
18
|
-
:type => :success)
|
|
19
|
+
INFLECTOR.underscore(action_class.to_s),
|
|
20
|
+
options.merge(:type => :successes))
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
private
|
|
22
24
|
|
|
23
|
-
def find_translated_message(message_or_key,
|
|
24
|
-
action_class,
|
|
25
|
-
i18n_options,
|
|
26
|
-
type)
|
|
25
|
+
def find_translated_message(message_or_key, action_class, options)
|
|
27
26
|
if message_or_key.is_a?(Symbol)
|
|
28
|
-
|
|
27
|
+
FunctionalLightService::LocalizationMap.instance.dig(
|
|
28
|
+
FunctionalLightService::Configuration.locale,
|
|
29
|
+
action_class.to_sym,
|
|
30
|
+
:light_service,
|
|
31
|
+
options[:type],
|
|
32
|
+
message_or_key
|
|
33
|
+
)
|
|
29
34
|
else
|
|
30
35
|
message_or_key
|
|
31
36
|
end
|
|
32
37
|
end
|
|
33
|
-
|
|
34
|
-
def translate(key, action_class, options = {})
|
|
35
|
-
type = options.delete(:type)
|
|
36
|
-
|
|
37
|
-
scope = i18n_scope_from_class(action_class, type)
|
|
38
|
-
options[:scope] = scope
|
|
39
|
-
|
|
40
|
-
I18n.t(key, **options)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def i18n_scope_from_class(action_class, type)
|
|
44
|
-
inflector = Dry::Inflector.new
|
|
45
|
-
"#{inflector.underscore(action_class.name)}.light_service.#{inflector.pluralize(type)}"
|
|
46
|
-
end
|
|
47
38
|
end
|
|
48
39
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceCase
|
|
6
|
+
extend ScopedReducable
|
|
7
|
+
|
|
8
|
+
class Arguments
|
|
9
|
+
attr_reader :value, :when, :else
|
|
10
|
+
|
|
11
|
+
def initialize(**args)
|
|
12
|
+
validate_arguments(**args)
|
|
13
|
+
@value = args[:value]
|
|
14
|
+
@when = args[:when]
|
|
15
|
+
@else = args[:else]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# rubocop:disable Style/MultilineIfModifier
|
|
21
|
+
def validate_arguments(**args)
|
|
22
|
+
raise(
|
|
23
|
+
ArgumentError,
|
|
24
|
+
"Expected keyword arguments: [:value, :when, :else]. Given: #{args.keys}"
|
|
25
|
+
) unless args.keys.intersection(mandatory_arguments).count == mandatory_arguments.count
|
|
26
|
+
end
|
|
27
|
+
# rubocop:enable Style/MultilineIfModifier
|
|
28
|
+
|
|
29
|
+
def mandatory_arguments
|
|
30
|
+
%i[value when else]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.run(organizer, **args)
|
|
35
|
+
arguments = Arguments.new(**args)
|
|
36
|
+
|
|
37
|
+
->(ctx) do
|
|
38
|
+
return ctx if ctx.stop_processing?
|
|
39
|
+
|
|
40
|
+
matched_case = arguments.when.keys.find { |k| k.eql?(ctx[arguments.value]) }
|
|
41
|
+
steps = arguments.when[matched_case] || arguments.else
|
|
42
|
+
|
|
43
|
+
ctx = scoped_reduce(organizer, ctx, steps)
|
|
44
|
+
|
|
45
|
+
ctx
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceIfElse
|
|
6
|
+
extend ScopedReducable
|
|
7
|
+
|
|
8
|
+
def self.run(organizer, condition_block, if_steps, else_steps)
|
|
9
|
+
->(ctx) do
|
|
10
|
+
return ctx if ctx.stop_processing?
|
|
11
|
+
|
|
12
|
+
ctx = if condition_block.call(ctx)
|
|
13
|
+
scoped_reduce(organizer, ctx, if_steps)
|
|
14
|
+
else
|
|
15
|
+
scoped_reduce(organizer, ctx, else_steps)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ctx
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceWhile
|
|
6
|
+
def self.run(organizer, condition_block, steps)
|
|
7
|
+
->(ctx) do
|
|
8
|
+
return ctx if ctx.stop_processing?
|
|
9
|
+
|
|
10
|
+
reset_skip(ctx)
|
|
11
|
+
|
|
12
|
+
Array(steps).each do |step|
|
|
13
|
+
break unless condition_block.call(ctx)
|
|
14
|
+
|
|
15
|
+
ctx = organizer.with(ctx).reduce([step])
|
|
16
|
+
break if ctx.stop_processing?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
reset_skip(ctx)
|
|
20
|
+
|
|
21
|
+
ctx
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.reset_skip(ctx)
|
|
26
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
27
|
+
end
|
|
28
|
+
private_class_method :reset_skip
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -4,9 +4,9 @@ module FunctionalLightService
|
|
|
4
4
|
module Organizer
|
|
5
5
|
module ScopedReducable
|
|
6
6
|
def scoped_reduce(organizer, ctx, steps)
|
|
7
|
-
ctx.reset_skip_remaining! unless ctx.failure?
|
|
7
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
8
8
|
ctx = organizer.with(ctx).reduce([steps])
|
|
9
|
-
ctx.reset_skip_remaining! unless ctx.failure?
|
|
9
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
10
10
|
|
|
11
11
|
ctx
|
|
12
12
|
end
|
|
@@ -93,7 +93,8 @@ module FunctionalLightService
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def skip_remaining?(context)
|
|
96
|
-
context.respond_to?(:skip_remaining?) && context.skip_remaining?
|
|
96
|
+
(context.respond_to?(:skip_remaining?) && context.skip_remaining?) ||
|
|
97
|
+
(context.respond_to?(:skip_all_remaining?) && context.skip_all_remaining?)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def write_skip_remaining_log(context, action)
|
|
@@ -36,16 +36,28 @@ module FunctionalLightService
|
|
|
36
36
|
ReduceIf.run(self, condition_block, steps)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def reduce_if_else(condition_block, if_steps, else_steps)
|
|
40
|
+
ReduceIfElse.run(self, condition_block, if_steps, else_steps)
|
|
41
|
+
end
|
|
42
|
+
|
|
39
43
|
def reduce_until(condition_block, steps)
|
|
40
44
|
ReduceUntil.run(self, condition_block, steps)
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
def reduce_case(**args)
|
|
48
|
+
ReduceCase.run(self, **args)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reduce_while(condition_block, steps)
|
|
52
|
+
ReduceWhile.run(self, condition_block, steps)
|
|
53
|
+
end
|
|
54
|
+
|
|
43
55
|
def iterate(collection_key, steps)
|
|
44
56
|
Iterate.run(self, collection_key, steps)
|
|
45
57
|
end
|
|
46
58
|
|
|
47
|
-
def execute(code_block)
|
|
48
|
-
Execute.run(code_block)
|
|
59
|
+
def execute(code_block = nil, &block)
|
|
60
|
+
Execute.run(code_block || block)
|
|
49
61
|
end
|
|
50
62
|
|
|
51
63
|
def with_callback(action, steps)
|
|
@@ -62,7 +74,10 @@ module FunctionalLightService
|
|
|
62
74
|
|
|
63
75
|
def add_to_context(**args)
|
|
64
76
|
args.map do |key, value|
|
|
65
|
-
execute(->(ctx)
|
|
77
|
+
execute(->(ctx) do
|
|
78
|
+
ctx[key.to_sym] = value
|
|
79
|
+
ctx.define_accessor_methods_for_keys([key])
|
|
80
|
+
end)
|
|
66
81
|
end
|
|
67
82
|
end
|
|
68
83
|
|
|
@@ -12,9 +12,12 @@ require 'functional-light-service/functional/enum'
|
|
|
12
12
|
require 'functional-light-service/functional/result'
|
|
13
13
|
require 'functional-light-service/functional/option'
|
|
14
14
|
require 'functional-light-service/functional/null'
|
|
15
|
+
require 'functional-light-service/functional/sequencer'
|
|
15
16
|
require 'functional-light-service/errors'
|
|
16
17
|
require 'functional-light-service/configuration'
|
|
18
|
+
require 'functional-light-service/i18n/localization_adapter'
|
|
17
19
|
require 'functional-light-service/localization_adapter'
|
|
20
|
+
require 'functional-light-service/localization_map'
|
|
18
21
|
require 'functional-light-service/context'
|
|
19
22
|
require 'functional-light-service/context/key_verifier'
|
|
20
23
|
require 'functional-light-service/organizer/scoped_reducable'
|
|
@@ -22,7 +25,10 @@ require 'functional-light-service/organizer/with_reducer'
|
|
|
22
25
|
require 'functional-light-service/organizer/with_reducer_log_decorator'
|
|
23
26
|
require 'functional-light-service/organizer/with_reducer_factory'
|
|
24
27
|
require 'functional-light-service/organizer/reduce_if'
|
|
28
|
+
require 'functional-light-service/organizer/reduce_if_else'
|
|
29
|
+
require 'functional-light-service/organizer/reduce_case'
|
|
25
30
|
require 'functional-light-service/organizer/reduce_until'
|
|
31
|
+
require 'functional-light-service/organizer/reduce_while'
|
|
26
32
|
require 'functional-light-service/organizer/iterate'
|
|
27
33
|
require 'functional-light-service/organizer/execute'
|
|
28
34
|
require 'functional-light-service/organizer/with_callback'
|
|
@@ -27,4 +27,28 @@ RSpec.describe FunctionalLightService::Organizer do
|
|
|
27
27
|
expect(result.number).to eq(1)
|
|
28
28
|
expect(result[:something]).to eq("hello")
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
it "defines accessors for the added keys" do
|
|
32
|
+
result = TestAddToContext.call
|
|
33
|
+
|
|
34
|
+
expect(result.something).to eq("hello")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises when the added key conflicts with a Context method" do
|
|
38
|
+
organizer = Class.new do
|
|
39
|
+
extend FunctionalLightService::Organizer
|
|
40
|
+
|
|
41
|
+
def self.call
|
|
42
|
+
reduce(steps)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.steps
|
|
46
|
+
[add_to_context(:message => "boom")]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
expect { organizer.call }
|
|
51
|
+
.to raise_error(FunctionalLightService::ReservedKeysInContextError,
|
|
52
|
+
/:message conflicts/)
|
|
53
|
+
end
|
|
30
54
|
end
|
|
@@ -31,6 +31,21 @@ RSpec.describe FunctionalLightService::Organizer do
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
class TestSkipAllFromNestedScope
|
|
35
|
+
extend FunctionalLightService::Organizer
|
|
36
|
+
|
|
37
|
+
def self.call
|
|
38
|
+
with(:number => 1)
|
|
39
|
+
.reduce([
|
|
40
|
+
reduce_if(->(_ctx) { true }, [
|
|
41
|
+
TestDoubles::AddsOneAction,
|
|
42
|
+
execute(lambda(&:skip_all_remaining!))
|
|
43
|
+
]),
|
|
44
|
+
TestDoubles::AddsOneAction
|
|
45
|
+
])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
34
49
|
class TestContextFailure
|
|
35
50
|
extend FunctionalLightService::Organizer
|
|
36
51
|
|
|
@@ -59,6 +74,13 @@ RSpec.describe FunctionalLightService::Organizer do
|
|
|
59
74
|
expect(result[:number]).to eq(3)
|
|
60
75
|
end
|
|
61
76
|
|
|
77
|
+
it 'does not reset skip_all_remaining at the end of a nested scope' do
|
|
78
|
+
result = TestSkipAllFromNestedScope.call
|
|
79
|
+
|
|
80
|
+
expect(result).to be_success
|
|
81
|
+
expect(result[:number]).to eq(2)
|
|
82
|
+
end
|
|
83
|
+
|
|
62
84
|
it 'respects failure across all nestings' do
|
|
63
85
|
result = TestContextFailure.call
|
|
64
86
|
|
|
@@ -43,4 +43,25 @@ RSpec.describe FunctionalLightService::Organizer do
|
|
|
43
43
|
result = TestExecute.call(empty_context)
|
|
44
44
|
expect(result).to be_success
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
it 'accepts a block instead of a lambda' do
|
|
48
|
+
organizer = Class.new do
|
|
49
|
+
extend FunctionalLightService::Organizer
|
|
50
|
+
|
|
51
|
+
def self.call(context)
|
|
52
|
+
with(context).reduce(steps)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.steps
|
|
56
|
+
[
|
|
57
|
+
execute { |ctx| ctx[:number] += 1 }
|
|
58
|
+
]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
result = organizer.call(:number => 1)
|
|
63
|
+
|
|
64
|
+
expect(result).to be_success
|
|
65
|
+
expect(result[:number]).to eq(2)
|
|
66
|
+
end
|
|
46
67
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe FunctionalLightService::Organizer do
|
|
5
|
+
class TestReduceCase
|
|
6
|
+
extend FunctionalLightService::Organizer
|
|
7
|
+
|
|
8
|
+
def self.call(context)
|
|
9
|
+
with(context).reduce(actions)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.actions
|
|
13
|
+
[
|
|
14
|
+
reduce_case(
|
|
15
|
+
:value => :incr_num,
|
|
16
|
+
:when => {
|
|
17
|
+
:one => [TestDoubles::AddsOneAction],
|
|
18
|
+
:two => [TestDoubles::AddsTwoAction],
|
|
19
|
+
:three => [TestDoubles::AddsThreeAction]
|
|
20
|
+
},
|
|
21
|
+
:else => [TestDoubles::FailureAction]
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'adds one if the incr_num is one' do
|
|
28
|
+
result = TestReduceCase.call(:number => 0, :incr_num => :one)
|
|
29
|
+
|
|
30
|
+
expect(result).to be_success
|
|
31
|
+
expect(result[:number]).to eq(1)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'adds two if the incr_num is two' do
|
|
35
|
+
result = TestReduceCase.call(:number => 0, :incr_num => :two)
|
|
36
|
+
|
|
37
|
+
expect(result).to be_success
|
|
38
|
+
expect(result[:number]).to eq(2)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'adds three if the incr_num is three' do
|
|
42
|
+
result = TestReduceCase.call(:number => 0, :incr_num => :three)
|
|
43
|
+
|
|
44
|
+
expect(result).to be_success
|
|
45
|
+
expect(result[:number]).to eq(3)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'will fail if the incr_num is neither one, two, or three' do
|
|
49
|
+
result = TestReduceCase.call(:number => 0, :incr_num => :four)
|
|
50
|
+
|
|
51
|
+
expect(result).to be_failure
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'raises ArgumentError when a mandatory keyword argument is missing' do
|
|
55
|
+
expect do
|
|
56
|
+
Class.new do
|
|
57
|
+
extend FunctionalLightService::Organizer
|
|
58
|
+
|
|
59
|
+
def self.actions
|
|
60
|
+
[reduce_case(:value => :incr_num, :when => {})]
|
|
61
|
+
end
|
|
62
|
+
end.actions
|
|
63
|
+
end.to raise_error(ArgumentError, /Expected keyword arguments/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe FunctionalLightService::Organizer do
|
|
5
|
+
class TestReduceIfElse
|
|
6
|
+
extend FunctionalLightService::Organizer
|
|
7
|
+
|
|
8
|
+
def self.call(context)
|
|
9
|
+
with(context).reduce(actions)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.actions
|
|
13
|
+
[
|
|
14
|
+
TestDoubles::AddsOneAction,
|
|
15
|
+
reduce_if_else(
|
|
16
|
+
->(ctx) { ctx.number == 1 },
|
|
17
|
+
[TestDoubles::AddsOneAction],
|
|
18
|
+
[TestDoubles::AddsTwoAction]
|
|
19
|
+
)
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:empty_context) { FunctionalLightService::Context.make }
|
|
25
|
+
|
|
26
|
+
it 'reduces the if_steps if the condition is true' do
|
|
27
|
+
result = TestReduceIfElse.call(:number => 0)
|
|
28
|
+
|
|
29
|
+
expect(result).to be_success
|
|
30
|
+
expect(result[:number]).to eq(2)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'reduces the else_steps if the condition is false' do
|
|
34
|
+
result = TestReduceIfElse.call(:number => 2)
|
|
35
|
+
|
|
36
|
+
expect(result).to be_success
|
|
37
|
+
expect(result[:number]).to eq(5)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'will not reduce over a failed context' do
|
|
41
|
+
empty_context.fail!('Something bad happened')
|
|
42
|
+
|
|
43
|
+
result = TestReduceIfElse.call(empty_context)
|
|
44
|
+
|
|
45
|
+
expect(result).to be_failure
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'does not reduce over a skipped context' do
|
|
49
|
+
empty_context.skip_remaining!('No more needed')
|
|
50
|
+
|
|
51
|
+
result = TestReduceIfElse.call(empty_context)
|
|
52
|
+
expect(result).to be_success
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "knows that it's being conditionally reduced from within an organizer" do
|
|
56
|
+
result = TestReduceIfElse.call(:number => 2)
|
|
57
|
+
|
|
58
|
+
expect(result.organized_by).to eq TestReduceIfElse
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe FunctionalLightService::Organizer do
|
|
5
|
+
class TestReduceWhile
|
|
6
|
+
extend FunctionalLightService::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) { FunctionalLightService::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 FunctionalLightService::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(lambda(&: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
|