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,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