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,122 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe ":promises macro" do
5
+ context "when the promised key is not in the context" do
6
+ it "raises an ArgumentError" do
7
+ module TestDoubles
8
+ class MakesCappuccinoAction1
9
+ extend FunctionalLightService::Action
10
+ expects :coffee, :milk
11
+ promises :cappuccino
12
+ executed do |context|
13
+ context[:macchiato] = "#{context.coffee} - #{context.milk}"
14
+ end
15
+ end
16
+ end
17
+
18
+ exception_msg = "promised :cappuccino to be in the context during " \
19
+ "TestDoubles::MakesCappuccinoAction1"
20
+ expect do
21
+ TestDoubles::MakesCappuccinoAction1.execute(:coffee => "espresso",
22
+ :milk => "2%")
23
+ end.to \
24
+ raise_error(FunctionalLightService::PromisedKeysNotInContextError, exception_msg)
25
+ end
26
+
27
+ it "can fail the context without fulfilling its promise" do
28
+ module TestDoubles
29
+ class MakesCappuccinoAction2
30
+ extend FunctionalLightService::Action
31
+ expects :coffee, :milk
32
+ promises :cappuccino
33
+ executed do |context|
34
+ context.fail!("Sorry, something bad has happened.")
35
+ end
36
+ end
37
+ end
38
+
39
+ result_context = TestDoubles::MakesCappuccinoAction2
40
+ .execute(:coffee => "espresso",
41
+ :milk => "2%")
42
+
43
+ expect(result_context).to be_failure
44
+ expect(result_context.keys).not_to include(:cappuccino)
45
+ end
46
+ end
47
+
48
+ context "when the promised key is in the context" do
49
+ it "can be set with an actual value" do
50
+ module TestDoubles
51
+ class MakesCappuccinoAction3
52
+ extend FunctionalLightService::Action
53
+ expects :coffee, :milk
54
+ promises :cappuccino
55
+ executed do |context|
56
+ context.cappuccino = "#{context.coffee} - with #{context.milk} milk"
57
+ context.cappuccino += " hot"
58
+ end
59
+ end
60
+ end
61
+
62
+ result_context = TestDoubles::MakesCappuccinoAction3
63
+ .execute(:coffee => "espresso",
64
+ :milk => "2%")
65
+
66
+ expect(result_context).to be_success
67
+ expect(result_context.cappuccino).to eq("espresso - with 2% milk hot")
68
+ end
69
+
70
+ it "can be set with nil" do
71
+ module TestDoubles
72
+ class MakesCappuccinoAction4
73
+ extend FunctionalLightService::Action
74
+ expects :coffee, :milk
75
+ promises :cappuccino
76
+ executed do |context|
77
+ context.cappuccino = nil
78
+ end
79
+ end
80
+ end
81
+ result_context = TestDoubles::MakesCappuccinoAction4
82
+ .execute(:coffee => "espresso",
83
+ :milk => "2%")
84
+
85
+ expect(result_context).to be_success
86
+ expect(result_context[:cappuccino]).to be_nil
87
+ end
88
+ end
89
+
90
+ context "when a reserved key is listed as a promised key" do
91
+ it "raises error indicating a reserved key has been promised" do
92
+ exception_msg = "promised or expected keys cannot be a reserved key: "\
93
+ "[:message]"
94
+ expect do
95
+ TestDoubles::MakesTeaPromisingReservedKey.execute(:tea => "black")
96
+ end.to \
97
+ raise_error(FunctionalLightService::ReservedKeysInContextError, exception_msg)
98
+ end
99
+
100
+ it "raises error indicating multiple reserved keys have been promised" do
101
+ exception_msg = "promised or expected keys cannot be a reserved key: " \
102
+ "[:message, :error_code, :current_action]"
103
+ expect do
104
+ ctx = { :tea => "black" }
105
+ TestDoubles::MakesTeaPromisingMultipleReservedKeys.execute(ctx)
106
+ end.to \
107
+ raise_error(FunctionalLightService::ReservedKeysInContextError, exception_msg)
108
+ end
109
+ end
110
+
111
+ context "when the `promised` macro is called multiple times" do
112
+ it "collects promised keys " do
113
+ result = TestDoubles::MultiplePromisesAction \
114
+ .execute(:coffee => "espresso", :milk => "2%")
115
+
116
+ expect(result.cappuccino).to \
117
+ eq("Cappucino needs espresso and a little milk")
118
+ expect(result.latte).to \
119
+ eq("Latte needs espresso and a lot of milk")
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe FunctionalLightService::Action do
5
+ let(:context) { FunctionalLightService::Context.make }
6
+
7
+ context "when the action context has failure" do
8
+ it "returns immediately" do
9
+ context.fail!("an error")
10
+
11
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
12
+
13
+ expect(context.to_hash.keys).to be_empty
14
+ end
15
+
16
+ it "returns the failure message in the context" do
17
+ context.fail!("an error")
18
+
19
+ returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
20
+
21
+ expect(returned_context.message).to eq("an error")
22
+ end
23
+ end
24
+
25
+ context "when the action has an explicit success message" do
26
+ it "returns the success message in the context" do
27
+ context.succeed!("successful")
28
+
29
+ returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
30
+
31
+ expect(returned_context.message).to eq("successful")
32
+ end
33
+ end
34
+
35
+ context "when the action context does not have failure" do
36
+ it "executes the block" do
37
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
38
+
39
+ expect(context.to_hash.keys).to eq [:number]
40
+ expect(context.fetch(:number)).to eq(2)
41
+ end
42
+ end
43
+
44
+ context "when the action context skips all" do
45
+ it "returns immediately" do
46
+ context.skip_remaining!
47
+
48
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
49
+
50
+ expect(context.to_hash.keys).to be_empty
51
+ end
52
+
53
+ it "does not execute skipped actions" do
54
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
55
+ expect(context.to_hash).to eq(:number => 2)
56
+
57
+ context.skip_remaining!
58
+
59
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
60
+ # Since the action was skipped, the number remains 2
61
+ expect(context.to_hash).to eq(:number => 2)
62
+ end
63
+ end
64
+
65
+ it "returns the context" do
66
+ result = TestDoubles::AddsTwoActionWithFetch.execute(context)
67
+
68
+ expect(result.to_hash).to eq(:number => 2)
69
+ end
70
+
71
+ context "when invoked with hash" do
72
+ it "creates FunctionalLightService::Context implicitly" do
73
+ ctx = { :some_key => "some value" }
74
+ result = TestDoubles::AddsTwoActionWithFetch.execute(ctx)
75
+
76
+ expect(result).to be_success
77
+ expect(result.keys).to eq(%i[some_key number])
78
+ end
79
+ end
80
+
81
+ context "when invoked without arguments" do
82
+ it "creates FunctionalLightService::Context implicitly" do
83
+ result = TestDoubles::AddsTwoActionWithFetch.execute
84
+
85
+ expect(result).to be_success
86
+ expect(result.keys).to eq([:number])
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Context do
5
+ subject(:context) { FunctionalLightService::Context.make }
6
+
7
+ describe 'to_s' do
8
+ it 'prints the context hash' do
9
+ expect(context.to_s).to eq('{}')
10
+ end
11
+ end
12
+
13
+ describe '#inspect' do
14
+ it 'inspects the hash with all the fields' do
15
+ inspected_context =
16
+ 'FunctionalLightService::Context({}, ' \
17
+ + 'success: true, ' \
18
+ + 'message: \'\', ' \
19
+ + 'error_code: nil, ' \
20
+ + 'skip_remaining: false, ' \
21
+ + 'aliases: {}' \
22
+ + ')'
23
+
24
+ expect(context.inspect).to eq(inspected_context)
25
+ end
26
+
27
+ it 'prints the error message' do
28
+ context.fail!('There was an error')
29
+
30
+ inspected_context =
31
+ 'FunctionalLightService::Context({}, ' \
32
+ + 'success: false, ' \
33
+ + 'message: \'There was an error\', ' \
34
+ + 'error_code: nil, ' \
35
+ + 'skip_remaining: false, ' \
36
+ + 'aliases: {}' \
37
+ + ')'
38
+
39
+ expect(context.inspect).to eq(inspected_context)
40
+ end
41
+
42
+ it 'prints skip_remaining' do
43
+ context.skip_remaining!('No need to process')
44
+
45
+ inspected_context =
46
+ 'FunctionalLightService::Context({}, ' \
47
+ + 'success: true, ' \
48
+ + 'message: \'No need to process\', ' \
49
+ + 'error_code: nil, ' \
50
+ + 'skip_remaining: true, ' \
51
+ + 'aliases: {}' \
52
+ + ')'
53
+
54
+ expect(context.inspect).to eq(inspected_context)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe FunctionalLightService::Context do
5
+ let(:context) { FunctionalLightService::Context.make }
6
+
7
+ describe "can be made" do
8
+ context "with no arguments" do
9
+ subject { FunctionalLightService::Context.make }
10
+ it { is_expected.to be_success }
11
+ specify "message is empty string" do
12
+ expect(context.message).to be_empty
13
+ end
14
+ end
15
+
16
+ context "with a hash" do
17
+ it "has the hash values" do
18
+ context = FunctionalLightService::Context.make(:one => 1)
19
+
20
+ expect(context[:one]).to eq(1)
21
+ end
22
+ end
23
+
24
+ context "with FAILURE" do
25
+ it "is failed" do
26
+ outcome = FunctionalLightService::Result::Failure(:message => '', :error => nil)
27
+ context = FunctionalLightService::Context.new({}, outcome)
28
+
29
+ expect(context).to be_failure
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "can't be made" do
35
+ specify "with invalid parameters" do
36
+ expect { FunctionalLightService::Context.make([]) }.to raise_error(ArgumentError)
37
+ end
38
+ end
39
+
40
+ it "can be asked for success?" do
41
+ outcome = FunctionalLightService::Result::Success(:message => '', :error => nil)
42
+ context = FunctionalLightService::Context.new({}, outcome)
43
+
44
+ expect(context).to be_success
45
+ end
46
+
47
+ it "can be asked for failure?" do
48
+ outcome = FunctionalLightService::Result::Failure(:message => '', :error => nil)
49
+ context = FunctionalLightService::Context.new({}, outcome)
50
+
51
+ expect(context).to be_failure
52
+ end
53
+
54
+ it "can be asked for skip_remaining?" do
55
+ context.skip_remaining!
56
+
57
+ expect(context.skip_remaining?).to be_truthy
58
+ end
59
+
60
+ it "can be pushed into a SUCCESS state" do
61
+ context.succeed!("a happy end")
62
+
63
+ expect(context).to be_success
64
+ end
65
+
66
+ it "can be pushed into a SUCCESS state without a message" do
67
+ context.succeed!
68
+
69
+ expect(context).to be_success
70
+ expect(context.message).to be_nil
71
+ end
72
+
73
+ it "can be pushed into a FAILURE state without a message" do
74
+ context.fail!
75
+
76
+ expect(context).to be_failure
77
+ expect(context.message).to be_nil
78
+ end
79
+
80
+ it "can be pushed into a FAILURE state with a message" do
81
+ context.fail!("a sad end")
82
+
83
+ expect(context).to be_failure
84
+ end
85
+
86
+ it "can be pushed into a FAILURE state with a message in an options hash" do
87
+ context.fail!("a sad end")
88
+
89
+ expect(context).to be_failure
90
+ expect(context.message).to eq("a sad end")
91
+ expect(context.error_code).to be_nil
92
+ end
93
+
94
+ it "can be pushed into a FAILURE state with an error code in options hash" do
95
+ context.fail!("a sad end", 10_005)
96
+
97
+ expect(context).to be_failure
98
+ expect(context.message).to eq("a sad end")
99
+ expect(context.error_code).to eq(10_005)
100
+ end
101
+
102
+ it "uses localization adapter to translate failure message" do
103
+ action_class = TestDoubles::AnAction
104
+ expect(FunctionalLightService::Configuration.localization_adapter)
105
+ .to receive(:failure)
106
+ .with(:failure_reason, action_class, {})
107
+ .and_return("message")
108
+
109
+ context = FunctionalLightService::Context.make
110
+ context.current_action = action_class
111
+ context.fail!(:failure_reason)
112
+
113
+ expect(context).to be_failure
114
+ expect(context.message).to eq("message")
115
+ end
116
+
117
+ it "uses localization adapter to translate success message" do
118
+ action_class = TestDoubles::AnAction
119
+ expect(FunctionalLightService::Configuration.localization_adapter)
120
+ .to receive(:success)
121
+ .with(:action_passed, action_class, {})
122
+ .and_return("message")
123
+
124
+ context = FunctionalLightService::Context.make
125
+ context.current_action = action_class
126
+ context.succeed!(:action_passed)
127
+
128
+ expect(context).to be_success
129
+ expect(context.message).to eq("message")
130
+ end
131
+
132
+ it "can set a flag to skip all subsequent actions" do
133
+ context.skip_remaining!
134
+
135
+ expect(context).to be_skip_remaining
136
+ end
137
+
138
+ context "stopping additional processing in an action" do
139
+ it "flags processing to stop on failure" do
140
+ context.fail!("on purpose")
141
+ expect(context.stop_processing?).to be_truthy
142
+ end
143
+
144
+ it "flags processing to stop when remaining actions should be skipped" do
145
+ context.skip_remaining!
146
+ expect(context.stop_processing?).to be_truthy
147
+ end
148
+ end
149
+
150
+ it "can fail with FailWithRollBackError" do
151
+ expect { context.fail_with_rollback!("roll me back") }.to \
152
+ raise_error(FunctionalLightService::FailWithRollbackError)
153
+ end
154
+
155
+ it "exptected outcome reader get Success and message empty and error nil" do
156
+ outcome = FunctionalLightService::Result::Success(:message => "", :error => nil)
157
+ expect(context.outcome).to eq(outcome)
158
+ end
159
+
160
+ it "can contain false values" do
161
+ context = FunctionalLightService::Context.make(:foo => false)
162
+ expect(context[:foo]).to eq false
163
+ end
164
+
165
+ it "allows a default value for #fetch" do
166
+ expect(context.fetch(:madeup, :default)).to eq(:default)
167
+ end
168
+
169
+ it "allows a default block value for #fetch" do
170
+ expect(context.fetch(:madeup) { :default }).to eq(:default)
171
+ end
172
+
173
+ context "when aliases are included via .make" do
174
+ let(:context) do
175
+ FunctionalLightService::Context.make(
176
+ :foo => "foobar",
177
+ :foo2 => false,
178
+ :_aliases => aliases
179
+ )
180
+ end
181
+ let(:aliases) { { :foo => :bar, :foo2 => :bar2 } }
182
+
183
+ it "contains the aliases" do
184
+ expect(context.aliases).to eq(aliases)
185
+ expect(context).to include(:foo, :bar)
186
+ end
187
+
188
+ it "returns the correct values for #[] and #fetch" do
189
+ expect(context[:bar]).to eq context[:foo]
190
+ expect(context.fetch(:bar)).to eq context[:foo]
191
+ end
192
+
193
+ it "can contain false values" do
194
+ expect(context[:bar2]).to eq false
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'functional-light-service/functional/enum'
3
+
4
+ Amount = FunctionalLightService.enum do
5
+ Due(:amount)
6
+ Paid(:amount)
7
+ Info(:amount)
8
+ end
9
+
10
+ class Amount
11
+ def self.from_f(f)
12
+ f >= 0 ? Amount::Due.new(f) : Amount::Paid.new(-1 * f)
13
+ end
14
+ end
15
+
16
+ FunctionalLightService.impl(Amount) do
17
+ def to_s
18
+ match do
19
+ Due() { |a| format("%0.2f", a) }
20
+ Paid() { |a| format("-%0.2f", a) }
21
+ Info() { |a| format("(%0.2f)", a) }
22
+ end
23
+ end
24
+
25
+ def to_f
26
+ match do
27
+ Info() { |_a| 0 }
28
+ Due() { |a| a }
29
+ Paid() { |a| -1 * a }
30
+ end
31
+ end
32
+
33
+ def +(other)
34
+ raise TypeError "Expected other to be an Amount, got #{other.class}" unless other.is_a? Amount
35
+
36
+ Amount.from_f(to_f + other.to_f)
37
+ end
38
+ end
39
+
40
+ describe Amount do
41
+ # rubocop:disable Naming/MethodName
42
+ def Due(a)
43
+ Amount::Due.new(a)
44
+ end
45
+
46
+ def Paid(a)
47
+ Amount::Paid.new(a)
48
+ end
49
+
50
+ def Info(a)
51
+ Amount::Info.new(a)
52
+ end
53
+ # rubocop:enable Naming/MethodName
54
+
55
+ it "due" do
56
+ amount = Amount::Due.new(100.2)
57
+ expect(amount.to_s).to eq "100.20"
58
+ end
59
+
60
+ it "paid" do
61
+ amount = Amount::Paid.new(100.1)
62
+ expect(amount.to_s).to eq "-100.10"
63
+ end
64
+
65
+ it "paid" do
66
+ amount = Amount::Info.new(100.31)
67
+ expect(amount.to_s).to eq "(100.31)"
68
+ end
69
+
70
+ it "+" do
71
+ expect(Due(10) + Paid(20)).to eq Paid(10)
72
+ expect(Due(10) + Paid(10)).to eq Due(0)
73
+ expect(Due(10) + Due(10)).to eq Due(20)
74
+ expect(Paid(10) + Paid(10)).to eq Paid(20)
75
+ expect(Paid(10) + Due(1) + Info(99)).to eq Paid(9)
76
+ end
77
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ module FunctionalLightService
4
+ module Procify
5
+ def py(m, *args)
6
+ args.count > 0 ? method(m).to_proc.curry[*args] : method(m)
7
+ end
8
+ end
9
+ end
10
+
11
+ class BookingController
12
+ include FunctionalLightService::Prelude::Result
13
+ include FunctionalLightService::Procify
14
+
15
+ Context = Struct.new(:booking, :ability, :format)
16
+
17
+ def index(id, format = :html)
18
+ get_booking(id) << log(:booking) >>
19
+ py(:ability) << log(:ability) >>
20
+ py(:present, format) << log(:presenter) >>
21
+ py(:render) << log(:render)
22
+ end
23
+
24
+ def log(step)
25
+ ->(data) { [step, data] }
26
+ end
27
+
28
+ def ability(ctx)
29
+ ctx.ability = {} # Ability.new(@booking)
30
+ Success(ctx)
31
+ end
32
+
33
+ def present(format, ctx)
34
+ ctx.format = format
35
+
36
+ Success(ctx)
37
+ end
38
+
39
+ def render(ctx)
40
+ send(ctx.format, ctx)
41
+ end
42
+
43
+ def html(ctx)
44
+ Success(ctx)
45
+ end
46
+
47
+ def get_booking(id)
48
+ ctx = Context.new
49
+ ctx.booking = { :ref_anixe => id }
50
+ Success(ctx)
51
+ # @booking = @bms.booking_by_id(id)
52
+ # rescue BSON::InvalidObjectId => ex
53
+ # @booking = nil
54
+ # @ui.error(404, ex.message)
55
+ end
56
+ end
57
+
58
+ describe BookingController do
59
+ it "does something" do
60
+ bc = BookingController.new
61
+ bc.index('1234', :html)
62
+ end
63
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ # A Unit of Work for validating an address
4
+ module ValidateAddress
5
+ extend FunctionalLightService::Prelude::Result
6
+
7
+ def self.call(candidate)
8
+ errors = {}
9
+ errors[:street] = "Street cannot be empty" unless candidate.key? :street
10
+ errors[:city] = "Street cannot be empty" unless candidate.key? :city
11
+ errors[:postal] = "Street cannot be empty" unless candidate.key? :postal
12
+
13
+ errors.empty? ? Success(candidate) : Failure(errors)
14
+ end
15
+ end
16
+
17
+ describe ValidateAddress do
18
+ include FunctionalLightService
19
+ subject { ValidateAddress.call(candidate) }
20
+ context 'sunny day' do
21
+ let(:candidate) do
22
+ { :title => "Hobbiton",
23
+ :street => "501 Buckland Rd",
24
+ :city => "Matamata",
25
+ :postal => "3472",
26
+ :country => "nz" }
27
+ end
28
+ specify { expect(subject).to be_a FunctionalLightService::Result::Success }
29
+ specify { expect(subject.value).to eq candidate }
30
+ end
31
+
32
+ context 'empty data' do
33
+ let(:candidate) { {} }
34
+ specify { expect(subject).to be_a FunctionalLightService::Result::Failure }
35
+ specify { expect(subject.value).to include(:street, :city, :postal) }
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Class Mixin' do
4
+ describe 'try' do
5
+ module MyApp
6
+ class Thing
7
+ include FunctionalLightService::Prelude::Result
8
+
9
+ def run
10
+ Success(11) >> method(:double)
11
+ end
12
+
13
+ def double(num)
14
+ Success(num * 2)
15
+ end
16
+ end
17
+ end
18
+
19
+ it "cleanly mixes into a class" do
20
+ result = MyApp::Thing.new.run
21
+ expect(result).to eq FunctionalLightService::Result::Success.new(22)
22
+ end
23
+ end
24
+ end