functional-light-service 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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