functional-light-service 0.2.4

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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +72 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +3 -0
  7. data/CHANGELOG.md +37 -0
  8. data/CODE_OF_CONDUCT.md +22 -0
  9. data/Gemfile +6 -0
  10. data/LICENSE +22 -0
  11. data/README.md +1424 -0
  12. data/Rakefile +12 -0
  13. data/VERSION +1 -0
  14. data/functional-light-service.gemspec +26 -0
  15. data/gemfiles/activesupport_5.gemfile +8 -0
  16. data/gemfiles/activesupport_5.gemfile.lock +82 -0
  17. data/lib/functional-light-service/action.rb +102 -0
  18. data/lib/functional-light-service/configuration.rb +24 -0
  19. data/lib/functional-light-service/context/key_verifier.rb +118 -0
  20. data/lib/functional-light-service/context.rb +165 -0
  21. data/lib/functional-light-service/errors.rb +6 -0
  22. data/lib/functional-light-service/functional/enum.rb +254 -0
  23. data/lib/functional-light-service/functional/maybe.rb +14 -0
  24. data/lib/functional-light-service/functional/monad.rb +66 -0
  25. data/lib/functional-light-service/functional/null.rb +74 -0
  26. data/lib/functional-light-service/functional/option.rb +99 -0
  27. data/lib/functional-light-service/functional/result.rb +122 -0
  28. data/lib/functional-light-service/localization_adapter.rb +44 -0
  29. data/lib/functional-light-service/organizer/execute.rb +14 -0
  30. data/lib/functional-light-service/organizer/iterate.rb +22 -0
  31. data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
  32. data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
  33. data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
  34. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
  35. data/lib/functional-light-service/organizer/with_callback.rb +26 -0
  36. data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
  37. data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
  38. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
  39. data/lib/functional-light-service/organizer.rb +105 -0
  40. data/lib/functional-light-service/testing/context_factory.rb +40 -0
  41. data/lib/functional-light-service/testing.rb +1 -0
  42. data/lib/functional-light-service/version.rb +3 -0
  43. data/lib/functional-light-service.rb +29 -0
  44. data/resources/fail_actions.png +0 -0
  45. data/resources/light-service.png +0 -0
  46. data/resources/organizer_and_actions.png +0 -0
  47. data/resources/skip_actions.png +0 -0
  48. data/spec/acceptance/add_numbers_spec.rb +11 -0
  49. data/spec/acceptance/after_actions_spec.rb +71 -0
  50. data/spec/acceptance/around_each_spec.rb +19 -0
  51. data/spec/acceptance/before_actions_spec.rb +98 -0
  52. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  53. data/spec/acceptance/fail_spec.rb +24 -0
  54. data/spec/acceptance/include_warning_spec.rb +29 -0
  55. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  56. data/spec/acceptance/message_localization_spec.rb +118 -0
  57. data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
  58. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  59. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  60. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  61. data/spec/acceptance/organizer/iterate_spec.rb +37 -0
  62. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  63. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  64. data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
  65. data/spec/acceptance/rollback_spec.rb +132 -0
  66. data/spec/acceptance/skip_all_warning_spec.rb +20 -0
  67. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  68. data/spec/action_expected_keys_spec.rb +63 -0
  69. data/spec/action_expects_and_promises_spec.rb +93 -0
  70. data/spec/action_promised_keys_spec.rb +122 -0
  71. data/spec/action_spec.rb +89 -0
  72. data/spec/context/inspect_spec.rb +57 -0
  73. data/spec/context_spec.rb +197 -0
  74. data/spec/examples/amount_spec.rb +77 -0
  75. data/spec/examples/controller_spec.rb +63 -0
  76. data/spec/examples/validate_address_spec.rb +37 -0
  77. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  78. data/spec/lib/deterministic/currify_spec.rb +88 -0
  79. data/spec/lib/deterministic/monad_axioms.rb +44 -0
  80. data/spec/lib/deterministic/monad_spec.rb +45 -0
  81. data/spec/lib/deterministic/null_spec.rb +58 -0
  82. data/spec/lib/deterministic/option_spec.rb +133 -0
  83. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  84. data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
  85. data/spec/lib/deterministic/result/result_shared.rb +24 -0
  86. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  87. data/spec/lib/deterministic/result_spec.rb +63 -0
  88. data/spec/lib/enum_spec.rb +112 -0
  89. data/spec/localization_adapter_spec.rb +83 -0
  90. data/spec/organizer/with_reducer_spec.rb +56 -0
  91. data/spec/organizer_key_aliases_spec.rb +29 -0
  92. data/spec/organizer_spec.rb +93 -0
  93. data/spec/readme_spec.rb +47 -0
  94. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  95. data/spec/sample/calculates_tax_spec.rb +30 -0
  96. data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
  97. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  98. data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
  99. data/spec/sample/tax/calculates_tax.rb +11 -0
  100. data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
  101. data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
  102. data/spec/spec_helper.rb +24 -0
  103. data/spec/support.rb +1 -0
  104. data/spec/test_doubles.rb +552 -0
  105. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  106. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  107. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  108. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  109. data/spec/testing/context_factory_spec.rb +55 -0
  110. metadata +285 -0
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'Action before_actions' do
5
+ describe 'works with simple organizers - from outside' do
6
+ it 'can be used to inject code block before each action' do
7
+ TestDoubles::AdditionOrganizer.before_actions = ->(ctx) do
8
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
9
+ end
10
+
11
+ result = TestDoubles::AdditionOrganizer.call(0)
12
+
13
+ expect(result.fetch(:number)).to eq(4)
14
+ end
15
+
16
+ it 'works with iterator' do
17
+ TestDoubles::TestIterate.before_actions = [
18
+ ->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
19
+ ]
20
+
21
+ result = TestDoubles::TestIterate.call(:number => 0,
22
+ :counters => [1, 2, 3, 4])
23
+
24
+ expect(result).to be_success
25
+ expect(result.number).to eq(-4)
26
+ end
27
+ end
28
+
29
+ describe 'can be added to organizers declaratively' do
30
+ module BeforeActions
31
+ class AdditionOrganizer
32
+ extend FunctionalLightService::Organizer
33
+ before_actions (->(ctx) do
34
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
35
+ end),
36
+ (->(ctx) do
37
+ ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
38
+ end)
39
+
40
+ def self.call(number)
41
+ with(:number => number).reduce(actions)
42
+ end
43
+
44
+ def self.actions
45
+ [
46
+ TestDoubles::AddsOneAction,
47
+ TestDoubles::AddsTwoAction,
48
+ TestDoubles::AddsThreeAction
49
+ ]
50
+ end
51
+ end
52
+ end
53
+
54
+ it 'accepts before_actions hook lambdas from organizer' do
55
+ result = BeforeActions::AdditionOrganizer.call(0)
56
+
57
+ expect(result.fetch(:number)).to eq(1)
58
+ end
59
+ end
60
+
61
+ describe 'works with callbacks' do
62
+ it 'can interact with actions from the outside' do
63
+ TestDoubles::TestWithCallback.before_actions = [
64
+ ->(ctx) { ctx.total -= 1000 if ctx.current_action == TestDoubles::AddToTotalAction }
65
+ ]
66
+ result = TestDoubles::TestWithCallback.call
67
+
68
+ expect(result.counter).to eq(3)
69
+ expect(result.total).to eq(-2994)
70
+ end
71
+ end
72
+
73
+ describe 'can halt all execution with a raised error' do
74
+ it 'does not call the rest of the callback steps' do
75
+ class SkipContextError < StandardError
76
+ attr_reader :ctx
77
+
78
+ def initialize(msg, ctx)
79
+ @ctx = ctx
80
+ super(msg)
81
+ end
82
+ end
83
+ TestDoubles::TestWithCallback.before_actions = [
84
+ ->(ctx) do
85
+ if ctx.current_action == TestDoubles::IncrementCountAction
86
+ ctx.total -= 1000
87
+ raise SkipContextError.new("stop context now", ctx)
88
+ end
89
+ end
90
+ ]
91
+ begin
92
+ TestDoubles::TestWithCallback.call
93
+ rescue SkipContextError => e
94
+ expect(e.ctx).not_to be_empty
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Log from an organizer with a custom logger" do
4
+ context "when overriding the global FunctionalLightService organizer" do
5
+ let(:global_logger_organizer) do
6
+ Class.new do
7
+ extend FunctionalLightService::Organizer
8
+
9
+ def self.call(number)
10
+ with(:number => number).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ TestDoubles::AddsOneAction,
16
+ TestDoubles::AddsTwoAction,
17
+ TestDoubles::AddsThreeAction
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:global_logger_string) { StringIO.new }
24
+
25
+ let(:custom_logger_string) { StringIO.new }
26
+ let(:custom_logger_organizer) do
27
+ custom_logger = Logger.new(custom_logger_string)
28
+
29
+ Class.new do
30
+ extend FunctionalLightService::Organizer
31
+ log_with custom_logger
32
+
33
+ def self.call(coffee, this_hot = :very_hot)
34
+ with(:milk => this_hot, :coffee => coffee)
35
+ .reduce(TestDoubles::MakesLatteAction,
36
+ TestDoubles::AddsTwoActionWithFetch)
37
+ end
38
+ end
39
+ end
40
+
41
+ before do
42
+ @original_global_logger = FunctionalLightService::Configuration.logger
43
+ FunctionalLightService::Configuration.logger = Logger.new(global_logger_string)
44
+ end
45
+
46
+ it "logs in own logger" do
47
+ global_logger_organizer.call(1)
48
+ custom_logger_organizer.call(:coffee => "Cappucino")
49
+
50
+ expect(custom_logger_string.string).to include("MakesLatteAction")
51
+ expect(custom_logger_string.string).to_not include("AddsOneAction")
52
+ expect(global_logger_string.string).to include("AddsOneAction")
53
+ expect(global_logger_string.string).to_not include("MakesLatteAction")
54
+ end
55
+
56
+ after do
57
+ FunctionalLightService::Configuration.logger = @original_global_logger
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "fail! returns immediately from executed block" do
4
+ class FailAction
5
+ extend FunctionalLightService::Action
6
+ promises :one, :two
7
+
8
+ executed do |ctx|
9
+ ctx.one = 1
10
+ # Have to set it in Context
11
+ ctx.two = nil
12
+
13
+ ctx.fail_and_return!('Something went wrong')
14
+ ctx.two = 2
15
+ end
16
+ end
17
+
18
+ it "returns immediately from executed block" do
19
+ result = FailAction.execute
20
+
21
+ expect(result).to be_failure
22
+ expect(result.two).to be_nil
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Including is discouraged" do
4
+ context "when including FunctionalLightService::Organizer" do
5
+ it "gives warning" do
6
+ expected_msg = "including FunctionalLightService::Organizer is deprecated. " \
7
+ "Please use `extend FunctionalLightService::Organizer` instead"
8
+ expect(ActiveSupport::Deprecation).to receive(:warn)
9
+ .with(expected_msg)
10
+
11
+ class OrganizerIncludingLS
12
+ include FunctionalLightService::Organizer
13
+ end
14
+ end
15
+ end
16
+
17
+ context "when including FunctionalLightService::Action" do
18
+ it "gives warning" do
19
+ expected_msg = "including FunctionalLightService::Action is deprecated. " \
20
+ "Please use `extend FunctionalLightService::Action` instead"
21
+ expect(ActiveSupport::Deprecation).to receive(:warn)
22
+ .with(expected_msg)
23
+
24
+ class ActionIncludingLS
25
+ include FunctionalLightService::Action
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+ require 'stringio'
4
+
5
+ describe "Logs from organizer" do
6
+ def collects_log
7
+ original_logger = FunctionalLightService::Configuration.logger
8
+
9
+ strio = StringIO.new
10
+ FunctionalLightService::Configuration.logger = Logger.new(strio)
11
+
12
+ yield
13
+
14
+ FunctionalLightService::Configuration.logger = original_logger
15
+
16
+ strio.string
17
+ end
18
+
19
+ context "when every action has expects or promises" do
20
+ subject(:log_message) do
21
+ collects_log do
22
+ TestDoubles::MakesTeaAndCappuccino
23
+ .call("black tea", "2% milk", "espresso coffee")
24
+ end
25
+ end
26
+
27
+ it "describes what organizer was invoked" do
28
+ organizer_log_message = "[FunctionalLightService] - calling organizer " \
29
+ "<TestDoubles::MakesTeaAndCappuccino>"
30
+ expect(log_message).to include(organizer_log_message)
31
+ end
32
+
33
+ it "describes the actions invoked" do
34
+ organizer_log_message = "[FunctionalLightService] - executing " \
35
+ "<TestDoubles::MakesTeaWithMilkAction>"
36
+ expect(log_message).to include(organizer_log_message)
37
+ organizer_log_message = "[FunctionalLightService] - executing " \
38
+ "<TestDoubles::MakesLatteAction>"
39
+ expect(log_message).to include(organizer_log_message)
40
+ end
41
+
42
+ it "lists the keys in context before the actions are executed" do
43
+ organizer_log_message = "[FunctionalLightService] - " \
44
+ "keys in context: :tea, :milk, :coffee"
45
+ expect(log_message).to include(organizer_log_message)
46
+ end
47
+
48
+ it "lists the expects actions are expecting" do
49
+ organizer_log_message = "[FunctionalLightService] - expects: :tea, :milk"
50
+ expect(log_message).to include(organizer_log_message)
51
+ organizer_log_message = "[FunctionalLightService] - expects: :coffee, :milk"
52
+ expect(log_message).to include(organizer_log_message)
53
+ end
54
+
55
+ it "lists the promises actions are promising" do
56
+ organizer_log_message = "[FunctionalLightService] - promises: :milk_tea"
57
+ expect(log_message).to include(organizer_log_message)
58
+ organizer_log_message = "[FunctionalLightService] - promises: :latte"
59
+ expect(log_message).to include(organizer_log_message)
60
+ end
61
+
62
+ it "lists the keys in contect after the actions are executed" do
63
+ organizer_log_message = "[FunctionalLightService] - keys in context: " \
64
+ ":tea, :milk, :coffee, :milk_tea, :latte"
65
+ expect(log_message).to include(organizer_log_message)
66
+ end
67
+ end
68
+
69
+ context "when NOT every action expects or promises" do
70
+ subject(:log_message) do
71
+ collects_log do
72
+ TestDoubles::MakesCappuccinoAddsTwo.call("2% milk", "espresso coffee")
73
+ end
74
+ end
75
+
76
+ it "describes what organizer was invoked" do
77
+ organizer_log_message = "[FunctionalLightService] - calling organizer " \
78
+ "<TestDoubles::MakesCappuccinoAddsTwo>"
79
+ expect(log_message).to include(organizer_log_message)
80
+ end
81
+
82
+ it "does not list empty expects or promises" do
83
+ organizer_log_message = "[FunctionalLightService] - expects:\n"
84
+ expect(log_message).not_to include(organizer_log_message)
85
+ organizer_log_message = "[FunctionalLightService] - promises:\n"
86
+ expect(log_message).not_to include(organizer_log_message)
87
+ end
88
+ end
89
+
90
+ context "when the context has failed" do
91
+ subject(:log_message) do
92
+ collects_log do
93
+ TestDoubles::MakesCappuccinoAddsTwoAndFails
94
+ .call("espresso coffee")
95
+ end
96
+ end
97
+
98
+ it "logs it with a warning" do
99
+ organizer_log_message = "WARN -- : [FunctionalLightService] - :-((( " \
100
+ "<TestDoubles::MakesLatteAction> has failed..."
101
+ expect(log_message).to include(organizer_log_message)
102
+ organizer_log_message = "WARN -- : [FunctionalLightService] - context message: " \
103
+ "Can't make a latte from a milk that's very hot!"
104
+ expect(log_message).to include(organizer_log_message)
105
+ organizer_log_message = "[FunctionalLightService] - :-((( " \
106
+ "<TestDoubles::AddsTwoAction> has failed..."
107
+ expect(log_message).not_to include(organizer_log_message)
108
+ end
109
+ end
110
+
111
+ context "when the context has failed with rollback" do
112
+ subject(:log_message) do
113
+ collects_log do
114
+ TestDoubles::MakesCappuccinoAddsTwoAndFails
115
+ .call("espresso coffee", :super_hot)
116
+ end
117
+ end
118
+
119
+ it "logs it with a warning" do
120
+ organizer_log_message = "WARN -- : [FunctionalLightService] - :-((( " \
121
+ "<TestDoubles::MakesLatteAction> has failed..."
122
+ expect(log_message).to include(organizer_log_message)
123
+ organizer_log_message = "WARN -- : [FunctionalLightService] - context message: " \
124
+ "Can't make a latte from a milk that's super hot!"
125
+ expect(log_message).to include(organizer_log_message)
126
+ organizer_log_message = "[FunctionalLightService] - :-((( " \
127
+ "<TestDoubles::AddsTwoAction> " \
128
+ "has failed..."
129
+ expect(log_message).not_to include(organizer_log_message)
130
+ end
131
+ end
132
+
133
+ context "when the context is skipping the rest" do
134
+ subject(:log_message) do
135
+ collects_log do
136
+ TestDoubles::MakesCappuccinoSkipsAddsTwo.call("espresso coffee")
137
+ end
138
+ end
139
+
140
+ it "logs it with a warning" do
141
+ organizer_log_message = "INFO -- : [FunctionalLightService] - ;-) " \
142
+ "<TestDoubles::MakesLatteAction> has decided " \
143
+ "to skip the rest of the actions"
144
+ expect(log_message).to include(organizer_log_message)
145
+ organizer_log_message = "INFO -- : [FunctionalLightService] - context message: " \
146
+ "Can't make a latte with a fatty milk like that!"
147
+ expect(log_message).to include(organizer_log_message)
148
+ organizer_log_message = "INFO -- : [FunctionalLightService] - ;-) " \
149
+ "<TestDoubles::AddsTwoAction> has decided " \
150
+ "to skip the rest of the actions"
151
+ expect(log_message).not_to include(organizer_log_message)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,118 @@
1
+ require "spec_helper"
2
+ require "test_doubles"
3
+
4
+ class TestsLocalizationAdapter
5
+ extend FunctionalLightService::Organizer
6
+
7
+ def self.call(pass_or_fail, message_or_key, i18n_options = {})
8
+ with(
9
+ :pass_or_fail => pass_or_fail,
10
+ :message_or_key => message_or_key,
11
+ :i18n_options => i18n_options
12
+ ).reduce(TestsLocalizationInvocationOptionsAction)
13
+ end
14
+ end
15
+
16
+ class TestsLocalizationInvocationOptionsAction
17
+ extend FunctionalLightService::Action
18
+ expects :pass_or_fail, :message_or_key, :i18n_options
19
+
20
+ executed do |context|
21
+ if context.pass_or_fail == true
22
+ context.succeed!(context.message_or_key, context.i18n_options)
23
+ else
24
+ context.fail!(context.message_or_key, context.i18n_options)
25
+ end
26
+ end
27
+ end
28
+
29
+ def pass_with(message_or_key, i18n_options = {})
30
+ TestsLocalizationAdapter.call(true, message_or_key, i18n_options)
31
+ end
32
+
33
+ def fail_with(message_or_key, i18n_options = {})
34
+ TestsLocalizationAdapter.call(false, message_or_key, i18n_options)
35
+ end
36
+
37
+ describe "Localization Adapter" do
38
+ before do
39
+ I18n.backend.store_translations(
40
+ :en,
41
+ :tests_localization_invocation_options_action =>
42
+ {
43
+ :light_service => {
44
+ :failures => {
45
+ :some_failure_reason => "This has failed",
46
+ :failure_with_interpolation => "Failed with %{reason}"
47
+ },
48
+ :successes => {
49
+ :some_success_reason => "This has passed",
50
+ :success_with_interpolation => "Passed with %{reason}"
51
+ }
52
+ }
53
+ }
54
+ )
55
+ end
56
+
57
+ describe "passing a simple string message" do
58
+ describe "by failing the context" do
59
+ it "returns the string" do
60
+ result = fail_with("string message")
61
+
62
+ expect(result).to be_failure
63
+ expect(result.message).to eq("string message")
64
+ end
65
+ end
66
+
67
+ describe "by passing the context" do
68
+ it "returns the string" do
69
+ result = pass_with("string message")
70
+
71
+ expect(result).to be_success
72
+ expect(result.message).to eq("string message")
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "passing a Symbol" do
78
+ describe "by failing the context" do
79
+ it "performs a translation" do
80
+ result = fail_with(:some_failure_reason)
81
+
82
+ expect(result).to be_failure
83
+ expect(result.message).to eq("This has failed")
84
+ end
85
+ end
86
+
87
+ describe "by passing the contenxt" do
88
+ it "performs a translation" do
89
+ result = pass_with(:some_success_reason)
90
+
91
+ expect(result).to be_success
92
+ expect(result.message).to eq("This has passed")
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "passing a Symbol with interpolation variables" do
98
+ describe "by failing the context" do
99
+ it "performs a translation with interpolation" do
100
+ result = fail_with(:failure_with_interpolation,
101
+ :reason => "bad account")
102
+
103
+ expect(result).to be_failure
104
+ expect(result.message).to eq("Failed with bad account")
105
+ end
106
+ end
107
+
108
+ describe "by passing the context" do
109
+ it "performs a translation with interpolation" do
110
+ result = pass_with(:success_with_interpolation,
111
+ :reason => "account in good standing")
112
+
113
+ expect(result).to be_success
114
+ expect(result.message).to eq("Passed with account in good standing")
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
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
7
+ expect(ActiveSupport::Deprecation)
8
+ .to receive(:warn)
9
+ .with(/^The <OrganizerWithoutCallMethod> class is an organizer/)
10
+
11
+ class OrganizerWithoutCallMethod
12
+ extend FunctionalLightService::Organizer
13
+
14
+ def self.do_something
15
+ reduce([])
16
+ end
17
+ end
18
+
19
+ OrganizerWithoutCallMethod.do_something
20
+ end
21
+ end
22
+
23
+ context "when the organizer has the `call` method" do
24
+ it "does not issue a warning" do
25
+ expect(ActiveSupport::Deprecation)
26
+ .not_to receive(:warn)
27
+
28
+ class OrganizerWithCallMethod
29
+ extend FunctionalLightService::Organizer
30
+
31
+ def self.call
32
+ reduce([])
33
+ end
34
+ end
35
+
36
+ OrganizerWithCallMethod.call
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestReduceIfWithAroundEach
6
+ extend FunctionalLightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context)
10
+ .around_each(TestDoubles::AroundEachLoggerHandler)
11
+ .reduce(actions)
12
+ end
13
+
14
+ def self.actions
15
+ [
16
+ TestDoubles::AddsOneAction,
17
+ reduce_if(->(ctx) { ctx.number == 1 },
18
+ TestDoubles::AddsOneAction)
19
+ ]
20
+ end
21
+ end
22
+
23
+ it 'can be used to log data' do
24
+ result =
25
+ TestReduceIfWithAroundEach
26
+ .call(:number => 0,
27
+ :logger => TestDoubles::TestLogger.new)
28
+
29
+ expect(result.fetch(:number)).to eq(2)
30
+ expect(result[:logger].logs).to eq(
31
+ [{
32
+ :action => TestDoubles::AddsOneAction,
33
+ :before => 0,
34
+ :after => 1
35
+ }, {
36
+ :action => TestDoubles::AddsOneAction,
37
+ :before => 1,
38
+ :after => 2
39
+ }]
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,65 @@
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
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Organizer do
5
+ class TestExecute
6
+ extend FunctionalLightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(steps)
10
+ end
11
+
12
+ def self.steps
13
+ [
14
+ TestDoubles::AddsOneAction,
15
+ execute(->(ctx) { ctx.number += 1 }),
16
+ execute(->(ctx) { ctx[:something] = 'hello' }),
17
+ TestDoubles::AddsOne.actions
18
+ ]
19
+ end
20
+ end
21
+
22
+ let(:empty_context) { FunctionalLightService::Context.make }
23
+
24
+ it 'calls the lambda in the execute block using the context' do
25
+ result = TestExecute.call(:number => 0)
26
+
27
+ expect(result).to be_success
28
+ expect(result.number).to eq(3)
29
+ expect(result[:something]).to eq('hello')
30
+ end
31
+
32
+ it 'will not execute a failed context' do
33
+ empty_context.fail!('Something bad happened')
34
+
35
+ result = TestExecute.call(empty_context)
36
+
37
+ expect(result).to be_failure
38
+ end
39
+
40
+ it 'does not execute over a skipped context' do
41
+ empty_context.skip_remaining!('No more needed')
42
+
43
+ result = TestExecute.call(empty_context)
44
+ expect(result).to be_success
45
+ end
46
+ end