functional-light-service 0.5.4 → 6.0.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +35 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/CHANGELOG.md +38 -0
  6. data/README.md +54 -2
  7. data/audit/bench.rb +99 -0
  8. data/audit/verify_findings.rb +172 -0
  9. data/functional-light-service.gemspec +15 -21
  10. data/lib/functional-light-service/action.rb +97 -101
  11. data/lib/functional-light-service/configuration.rb +26 -24
  12. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  13. data/lib/functional-light-service/context.rb +63 -20
  14. data/lib/functional-light-service/deprecations.rb +26 -0
  15. data/lib/functional-light-service/errors.rb +8 -6
  16. data/lib/functional-light-service/functional/enum.rb +286 -250
  17. data/lib/functional-light-service/functional/maybe.rb +21 -15
  18. data/lib/functional-light-service/functional/monad.rb +77 -66
  19. data/lib/functional-light-service/functional/null.rb +88 -74
  20. data/lib/functional-light-service/functional/option.rb +100 -97
  21. data/lib/functional-light-service/functional/result.rb +129 -116
  22. data/lib/functional-light-service/localization_adapter.rb +48 -47
  23. data/lib/functional-light-service/organizer/execute.rb +16 -14
  24. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  25. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  26. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  27. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  28. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  29. data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
  30. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  31. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
  32. data/lib/functional-light-service/organizer.rb +114 -114
  33. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  34. data/lib/functional-light-service/testing.rb +3 -1
  35. data/lib/functional-light-service/version.rb +5 -3
  36. data/lib/functional-light-service.rb +30 -28
  37. data/spec/acceptance/after_actions_spec.rb +87 -71
  38. data/spec/acceptance/before_actions_spec.rb +115 -98
  39. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  40. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  41. data/spec/acceptance/fail_spec.rb +52 -50
  42. data/spec/acceptance/message_localization_spec.rb +119 -118
  43. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  44. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
  45. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  46. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  47. data/spec/acceptance/rollback_spec.rb +183 -132
  48. data/spec/action_expects_and_promises_spec.rb +97 -93
  49. data/spec/action_promised_keys_spec.rb +126 -122
  50. data/spec/context_spec.rb +289 -197
  51. data/spec/examples/controller_spec.rb +63 -63
  52. data/spec/examples/validate_address_spec.rb +38 -37
  53. data/spec/lib/deterministic/currify_spec.rb +90 -88
  54. data/spec/lib/deterministic/null_spec.rb +6 -1
  55. data/spec/lib/deterministic/option_spec.rb +140 -137
  56. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  57. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  58. data/spec/lib/deterministic/result_spec.rb +2 -2
  59. data/spec/lib/edge_cases_spec.rb +156 -0
  60. data/spec/lib/enum_spec.rb +1 -1
  61. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  62. data/spec/organizer_spec.rb +115 -114
  63. data/spec/readme_spec.rb +45 -47
  64. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  65. data/spec/sample/calculates_tax_spec.rb +1 -1
  66. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  67. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  68. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  69. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  70. data/spec/spec_helper.rb +6 -0
  71. data/spec/test_doubles.rb +628 -599
  72. data/spec/testing/context_factory_spec.rb +21 -0
  73. metadata +45 -161
  74. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  75. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,65 +1,68 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- class TestSkipBefore
6
- extend FunctionalLightService::Organizer
7
- def self.call
8
- with(:number => 1)
9
- .reduce([
10
- TestDoubles::SkipAllAction,
11
- reduce_until(->(ctx) { ctx.number == 3 },
12
- TestDoubles::AddsOneAction)
13
- ])
14
- end
15
- end
16
-
17
- class TestSkipAfter
18
- extend FunctionalLightService::Organizer
19
- def self.call
20
- with(:number => 1)
21
- .reduce([
22
- TestDoubles::AddsOneAction,
23
- reduce_until(->(ctx) { ctx.number == 3 }, [
24
- TestDoubles::AddsOneAction
25
- ]),
26
- TestDoubles::SkipAllAction,
27
- TestDoubles::AddsOneAction
28
- ])
29
- end
30
- end
31
-
32
- class TestContextFailure
33
- extend FunctionalLightService::Organizer
34
- def self.call
35
- with(:number => 1)
36
- .reduce([
37
- TestDoubles::FailureAction,
38
- reduce_until(->(ctx) { ctx[:number] == 3 },
39
- TestDoubles::AddsOneAction),
40
- TestDoubles::AddsOneAction
41
- ])
42
- end
43
- end
44
-
45
- it 'skips all the rest of the actions' do
46
- result = TestSkipBefore.call
47
-
48
- expect(result).to be_success
49
- expect(result[:number]).to eq(1)
50
- end
51
-
52
- it 'skips after an action in nested context' do
53
- result = TestSkipAfter.call
54
-
55
- expect(result).to be_success
56
- expect(result[:number]).to eq(3)
57
- end
58
-
59
- it 'respects failure across all nestings' do
60
- result = TestContextFailure.call
61
-
62
- expect(result).to be_failure
63
- expect(result[:number]).to eq(1)
64
- end
65
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestSkipBefore
6
+ extend FunctionalLightService::Organizer
7
+
8
+ def self.call
9
+ with(:number => 1)
10
+ .reduce([
11
+ TestDoubles::SkipAllAction,
12
+ reduce_until(->(ctx) { ctx.number == 3 },
13
+ TestDoubles::AddsOneAction)
14
+ ])
15
+ end
16
+ end
17
+
18
+ class TestSkipAfter
19
+ extend FunctionalLightService::Organizer
20
+
21
+ def self.call
22
+ with(:number => 1)
23
+ .reduce([
24
+ TestDoubles::AddsOneAction,
25
+ reduce_until(->(ctx) { ctx.number == 3 }, [
26
+ TestDoubles::AddsOneAction
27
+ ]),
28
+ TestDoubles::SkipAllAction,
29
+ TestDoubles::AddsOneAction
30
+ ])
31
+ end
32
+ end
33
+
34
+ class TestContextFailure
35
+ extend FunctionalLightService::Organizer
36
+
37
+ def self.call
38
+ with(:number => 1)
39
+ .reduce([
40
+ TestDoubles::FailureAction,
41
+ reduce_until(->(ctx) { ctx[:number] == 3 },
42
+ TestDoubles::AddsOneAction),
43
+ TestDoubles::AddsOneAction
44
+ ])
45
+ end
46
+ end
47
+
48
+ it 'skips all the rest of the actions' do
49
+ result = TestSkipBefore.call
50
+
51
+ expect(result).to be_success
52
+ expect(result[:number]).to eq(1)
53
+ end
54
+
55
+ it 'skips after an action in nested context' do
56
+ result = TestSkipAfter.call
57
+
58
+ expect(result).to be_success
59
+ expect(result[:number]).to eq(3)
60
+ end
61
+
62
+ it 'respects failure across all nestings' do
63
+ result = TestContextFailure.call
64
+
65
+ expect(result).to be_failure
66
+ expect(result[:number]).to eq(1)
67
+ end
68
+ end
@@ -1,89 +1,89 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- class TestReduceIf
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(->(ctx) { ctx.number == 1 },
16
- TestDoubles::AddsOneAction)
17
- ]
18
- end
19
- end
20
-
21
- let(:empty_context) { FunctionalLightService::Context.make }
22
-
23
- it 'reduces if the block evaluates to true' do
24
- result = TestReduceIf.call(:number => 0)
25
-
26
- expect(result).to be_success
27
- expect(result[:number]).to eq(2)
28
- end
29
-
30
- it 'does not reduce if the block evaluates to false' do
31
- result = TestReduceIf.call(:number => 2)
32
-
33
- expect(result).to be_success
34
- expect(result[:number]).to eq(3)
35
- end
36
-
37
- it 'will not reduce over a failed context' do
38
- empty_context.fail!('Something bad happened')
39
-
40
- result = TestReduceIf.call(empty_context)
41
-
42
- expect(result).to be_failure
43
- end
44
-
45
- it 'does not reduce over a skipped context' do
46
- empty_context.skip_remaining!('No more needed')
47
-
48
- result = TestReduceIf.call(empty_context)
49
- expect(result).to be_success
50
- end
51
-
52
- it "knows that it's being conditionally reduced from within an organizer" do
53
- result = TestReduceIf.call(:number => 2)
54
-
55
- expect(result.organized_by).to eq TestReduceIf
56
- end
57
-
58
- it 'skips actions within in its own scope' do
59
- org = Class.new do
60
- extend FunctionalLightService::Organizer
61
-
62
- def self.call
63
- reduce(actions)
64
- end
65
-
66
- def self.actions
67
- [
68
- reduce_if(
69
- ->(c) { !c.nil? },
70
- [
71
- execute(->(c) { c[:first_reduce_if] = true }),
72
- execute(->(c) { c.skip_remaining! }),
73
- execute(->(c) { c[:second_reduce_if] = true })
74
- ]
75
- ),
76
- execute(->(c) { c[:last_outside] = true })
77
- ]
78
- end
79
- end
80
-
81
- result = org.call
82
-
83
- aggregate_failures do
84
- expect(result[:first_reduce_if]).to be true
85
- expect(result[:second_reduce_if]).to be_nil
86
- expect(result[:last_outside]).to be true
87
- end
88
- end
89
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestReduceIf
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(->(ctx) { ctx.number == 1 },
16
+ TestDoubles::AddsOneAction)
17
+ ]
18
+ end
19
+ end
20
+
21
+ let(:empty_context) { FunctionalLightService::Context.make }
22
+
23
+ it 'reduces if the block evaluates to true' do
24
+ result = TestReduceIf.call(:number => 0)
25
+
26
+ expect(result).to be_success
27
+ expect(result[:number]).to eq(2)
28
+ end
29
+
30
+ it 'does not reduce if the block evaluates to false' do
31
+ result = TestReduceIf.call(:number => 2)
32
+
33
+ expect(result).to be_success
34
+ expect(result[:number]).to eq(3)
35
+ end
36
+
37
+ it 'will not reduce over a failed context' do
38
+ empty_context.fail!('Something bad happened')
39
+
40
+ result = TestReduceIf.call(empty_context)
41
+
42
+ expect(result).to be_failure
43
+ end
44
+
45
+ it 'does not reduce over a skipped context' do
46
+ empty_context.skip_remaining!('No more needed')
47
+
48
+ result = TestReduceIf.call(empty_context)
49
+ expect(result).to be_success
50
+ end
51
+
52
+ it "knows that it's being conditionally reduced from within an organizer" do
53
+ result = TestReduceIf.call(:number => 2)
54
+
55
+ expect(result.organized_by).to eq TestReduceIf
56
+ end
57
+
58
+ it 'skips actions within in its own scope' do
59
+ org = Class.new do
60
+ extend FunctionalLightService::Organizer
61
+
62
+ def self.call
63
+ reduce(actions)
64
+ end
65
+
66
+ def self.actions
67
+ [
68
+ reduce_if(
69
+ ->(c) { !c.nil? },
70
+ [
71
+ execute(->(c) { c[:first_reduce_if] = true }),
72
+ execute(->(c) { c.skip_remaining! }), # rubocop:disable Style/SymbolProc
73
+ execute(->(c) { c[:second_reduce_if] = true })
74
+ ]
75
+ ),
76
+ execute(->(c) { c[:last_outside] = true })
77
+ ]
78
+ end
79
+ end
80
+
81
+ result = org.call
82
+
83
+ aggregate_failures do
84
+ expect(result[:first_reduce_if]).to be true
85
+ expect(result[:second_reduce_if]).to be_nil
86
+ expect(result[:last_outside]).to be true
87
+ end
88
+ end
89
+ end
@@ -1,110 +1,113 @@
1
- require 'spec_helper'
2
- require 'test_doubles'
3
-
4
- RSpec.describe FunctionalLightService::Organizer do
5
- describe 'a simple case with a single callback' do
6
- it 'calls the actions defined with callback' do
7
- result = TestDoubles::TestWithCallback.call
8
-
9
- expect(result.counter).to eq(3)
10
- expect(result.total).to eq(6)
11
- end
12
- end
13
-
14
- describe 'a more complex example with nested callbacks' do
15
- class TestWithNestedCallback
16
- extend FunctionalLightService::Organizer
17
-
18
- def self.call(context = {})
19
- with(context).reduce(actions)
20
- end
21
-
22
- def self.actions
23
- [
24
- SetUpNestedContextAction,
25
- with_callback(IterateOuterCollectionAction,
26
- [IncrementOuterCountAction,
27
- with_callback(TestDoubles::IterateCollectionAction,
28
- [TestDoubles::IncrementCountAction,
29
- TestDoubles::AddToTotalAction])])
30
- ]
31
- end
32
- end
33
-
34
- class SetUpNestedContextAction
35
- extend FunctionalLightService::Action
36
- promises :outer_numbers, :outer_counter,
37
- :numbers, :counter, :total
38
-
39
- executed do |ctx|
40
- ctx.outer_numbers = [12, 17]
41
- ctx.outer_counter = 0
42
- ctx.numbers = [1, 2, 3]
43
- ctx.counter = 0
44
- ctx.total = 0
45
- end
46
- end
47
-
48
- class IterateOuterCollectionAction
49
- extend FunctionalLightService::Action
50
- expects :outer_numbers, :callback
51
- promises :outer_number
52
-
53
- executed do |ctx|
54
- ctx.outer_numbers.each do |outer_number|
55
- ctx.outer_number = outer_number
56
- ctx.callback.call(ctx)
57
- end
58
- end
59
- end
60
-
61
- class IncrementOuterCountAction
62
- extend FunctionalLightService::Action
63
- expects :outer_counter
64
-
65
- executed do |ctx|
66
- ctx.outer_counter = ctx.outer_counter + 1
67
- end
68
- end
69
-
70
- it 'calls both the action and the nested callbacks' do
71
- result = TestWithNestedCallback.call
72
-
73
- expect(result.outer_counter).to eq(2)
74
- # Counts and total are the duplicates of
75
- # what you'll see in the simple spec,
76
- # as the internal callback logic is called
77
- # twice due to 2 items in the outer_numbers
78
- # collection.
79
- expect(result.counter).to eq(6)
80
- expect(result.total).to eq(12)
81
- end
82
- end
83
-
84
- describe 'with failed or skipped context' do
85
- class TestWithFailureCallback
86
- extend FunctionalLightService::Organizer
87
-
88
- def self.call(context = {})
89
- with(context).reduce(actions)
90
- end
91
-
92
- def self.actions
93
- [
94
- TestDoubles::SetUpContextAction,
95
- with_callback(TestDoubles::IterateCollectionAction,
96
- [TestDoubles::IncrementCountAction,
97
- TestDoubles::FailureAction])
98
- ]
99
- end
100
- end
101
-
102
- it 'will not process the routine' do
103
- result = TestWithFailureCallback.call
104
-
105
- expect(result).to be_failure
106
- expect(result.counter).to eq(1)
107
- expect(result.total).to eq(0)
108
- end
109
- end
110
- end
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ describe 'a simple case with a single callback' do
6
+ it 'calls the actions defined with callback' do
7
+ result = TestDoubles::TestWithCallback.call
8
+
9
+ expect(result.counter).to eq(3)
10
+ expect(result.total).to eq(6)
11
+ end
12
+ end
13
+
14
+ describe 'a more complex example with nested callbacks' do
15
+ class TestWithNestedCallback
16
+ extend FunctionalLightService::Organizer
17
+
18
+ def self.call(context = {})
19
+ with(context).reduce(actions)
20
+ end
21
+
22
+ def self.actions
23
+ [
24
+ SetUpNestedContextAction,
25
+ with_callback(IterateOuterCollectionAction,
26
+ [IncrementOuterCountAction,
27
+ with_callback(TestDoubles::IterateCollectionAction,
28
+ [TestDoubles::IncrementCountAction,
29
+ TestDoubles::AddToTotalAction])])
30
+ ]
31
+ end
32
+ end
33
+
34
+ class SetUpNestedContextAction
35
+ extend FunctionalLightService::Action
36
+
37
+ promises :outer_numbers, :outer_counter,
38
+ :numbers, :counter, :total
39
+
40
+ executed do |ctx|
41
+ ctx.outer_numbers = [12, 17]
42
+ ctx.outer_counter = 0
43
+ ctx.numbers = [1, 2, 3]
44
+ ctx.counter = 0
45
+ ctx.total = 0
46
+ end
47
+ end
48
+
49
+ class IterateOuterCollectionAction
50
+ extend FunctionalLightService::Action
51
+
52
+ expects :outer_numbers, :callback
53
+ promises :outer_number
54
+
55
+ executed do |ctx|
56
+ ctx.outer_numbers.each do |outer_number|
57
+ ctx.outer_number = outer_number
58
+ ctx.callback.call(ctx)
59
+ end
60
+ end
61
+ end
62
+
63
+ class IncrementOuterCountAction
64
+ extend FunctionalLightService::Action
65
+
66
+ expects :outer_counter
67
+
68
+ executed do |ctx|
69
+ ctx.outer_counter = ctx.outer_counter + 1
70
+ end
71
+ end
72
+
73
+ it 'calls both the action and the nested callbacks' do
74
+ result = TestWithNestedCallback.call
75
+
76
+ expect(result.outer_counter).to eq(2)
77
+ # Counts and total are the duplicates of
78
+ # what you'll see in the simple spec,
79
+ # as the internal callback logic is called
80
+ # twice due to 2 items in the outer_numbers
81
+ # collection.
82
+ expect(result.counter).to eq(6)
83
+ expect(result.total).to eq(12)
84
+ end
85
+ end
86
+
87
+ describe 'with failed or skipped context' do
88
+ class TestWithFailureCallback
89
+ extend FunctionalLightService::Organizer
90
+
91
+ def self.call(context = {})
92
+ with(context).reduce(actions)
93
+ end
94
+
95
+ def self.actions
96
+ [
97
+ TestDoubles::SetUpContextAction,
98
+ with_callback(TestDoubles::IterateCollectionAction,
99
+ [TestDoubles::IncrementCountAction,
100
+ TestDoubles::FailureAction])
101
+ ]
102
+ end
103
+ end
104
+
105
+ it 'will not process the routine' do
106
+ result = TestWithFailureCallback.call
107
+
108
+ expect(result).to be_failure
109
+ expect(result.counter).to eq(1)
110
+ expect(result.total).to eq(0)
111
+ end
112
+ end
113
+ end
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
  require 'test_doubles'
3
3
 
4
- describe "Organizer should invoke with/reduce from a call method" do
5
- context "when the organizer does not have a `call` method" do
6
- it "gives warning" do
4
+ describe "Organizer entry point" do
5
+ context "when the organizer entry method is not named `call`" do
6
+ it "works without emitting any warning" do
7
7
  class OrganizerWithoutCallMethod
8
8
  extend FunctionalLightService::Organizer
9
9
 
@@ -11,14 +11,16 @@ describe "Organizer should invoke with/reduce from a call method" do
11
11
  reduce([])
12
12
  end
13
13
  end
14
- expect do
15
- OrganizerWithoutCallMethod.do_something
16
- end.to output(/The <OrganizerWithoutCallMethod> class is an organizer/).to_stdout
14
+
15
+ result = nil
16
+ expect { result = OrganizerWithoutCallMethod.do_something }
17
+ .not_to output.to_stdout
18
+ expect(result).to be_a_kind_of(FunctionalLightService::Context)
17
19
  end
18
20
  end
19
21
 
20
22
  context "when the organizer has the `call` method" do
21
- it "does not issue a warning" do
23
+ it "works without emitting any warning" do
22
24
  class OrganizerWithCallMethod
23
25
  extend FunctionalLightService::Organizer
24
26
 
@@ -26,6 +28,7 @@ describe "Organizer should invoke with/reduce from a call method" do
26
28
  reduce([])
27
29
  end
28
30
  end
31
+
29
32
  expect(OrganizerWithCallMethod.call).to be_a_kind_of(FunctionalLightService::Context)
30
33
  end
31
34
  end