functional-light-service 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +72 -0
- data/.travis.yml +22 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +1424 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/functional-light-service.gemspec +26 -0
- data/gemfiles/activesupport_5.gemfile +8 -0
- data/gemfiles/activesupport_5.gemfile.lock +82 -0
- data/lib/functional-light-service/action.rb +102 -0
- data/lib/functional-light-service/configuration.rb +24 -0
- data/lib/functional-light-service/context/key_verifier.rb +118 -0
- data/lib/functional-light-service/context.rb +165 -0
- data/lib/functional-light-service/errors.rb +6 -0
- data/lib/functional-light-service/functional/enum.rb +254 -0
- data/lib/functional-light-service/functional/maybe.rb +14 -0
- data/lib/functional-light-service/functional/monad.rb +66 -0
- data/lib/functional-light-service/functional/null.rb +74 -0
- data/lib/functional-light-service/functional/option.rb +99 -0
- data/lib/functional-light-service/functional/result.rb +122 -0
- data/lib/functional-light-service/localization_adapter.rb +44 -0
- data/lib/functional-light-service/organizer/execute.rb +14 -0
- data/lib/functional-light-service/organizer/iterate.rb +22 -0
- data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
- data/lib/functional-light-service/organizer/with_callback.rb +26 -0
- data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
- data/lib/functional-light-service/organizer.rb +105 -0
- data/lib/functional-light-service/testing/context_factory.rb +40 -0
- data/lib/functional-light-service/testing.rb +1 -0
- data/lib/functional-light-service/version.rb +3 -0
- data/lib/functional-light-service.rb +29 -0
- data/resources/fail_actions.png +0 -0
- data/resources/light-service.png +0 -0
- data/resources/organizer_and_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/add_numbers_spec.rb +11 -0
- data/spec/acceptance/after_actions_spec.rb +71 -0
- data/spec/acceptance/around_each_spec.rb +19 -0
- data/spec/acceptance/before_actions_spec.rb +98 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +24 -0
- data/spec/acceptance/include_warning_spec.rb +29 -0
- data/spec/acceptance/log_from_organizer_spec.rb +154 -0
- data/spec/acceptance/message_localization_spec.rb +118 -0
- data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
- data/spec/acceptance/organizer/execute_spec.rb +46 -0
- data/spec/acceptance/organizer/iterate_spec.rb +37 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
- data/spec/acceptance/rollback_spec.rb +132 -0
- data/spec/acceptance/skip_all_warning_spec.rb +20 -0
- data/spec/acceptance/testing/context_factory_spec.rb +54 -0
- data/spec/action_expected_keys_spec.rb +63 -0
- data/spec/action_expects_and_promises_spec.rb +93 -0
- data/spec/action_promised_keys_spec.rb +122 -0
- data/spec/action_spec.rb +89 -0
- data/spec/context/inspect_spec.rb +57 -0
- data/spec/context_spec.rb +197 -0
- data/spec/examples/amount_spec.rb +77 -0
- data/spec/examples/controller_spec.rb +63 -0
- data/spec/examples/validate_address_spec.rb +37 -0
- data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
- data/spec/lib/deterministic/currify_spec.rb +88 -0
- data/spec/lib/deterministic/monad_axioms.rb +44 -0
- data/spec/lib/deterministic/monad_spec.rb +45 -0
- data/spec/lib/deterministic/null_spec.rb +58 -0
- data/spec/lib/deterministic/option_spec.rb +133 -0
- data/spec/lib/deterministic/result/failure_spec.rb +65 -0
- data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
- data/spec/lib/deterministic/result/result_shared.rb +24 -0
- data/spec/lib/deterministic/result/success_spec.rb +41 -0
- data/spec/lib/deterministic/result_spec.rb +63 -0
- data/spec/lib/enum_spec.rb +112 -0
- data/spec/localization_adapter_spec.rb +83 -0
- data/spec/organizer/with_reducer_spec.rb +56 -0
- data/spec/organizer_key_aliases_spec.rb +29 -0
- data/spec/organizer_spec.rb +93 -0
- data/spec/readme_spec.rb +47 -0
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
- data/spec/sample/calculates_tax_spec.rb +30 -0
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
- data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
- data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
- data/spec/sample/tax/calculates_tax.rb +11 -0
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
- data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support.rb +1 -0
- data/spec/test_doubles.rb +552 -0
- data/spec/testing/context_factory/iterate_spec.rb +39 -0
- data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
- data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
- data/spec/testing/context_factory/with_callback_spec.rb +38 -0
- data/spec/testing/context_factory_spec.rb +55 -0
- metadata +285 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FunctionalLightService::Result do
|
4
|
+
include FunctionalLightService::Prelude::Result
|
5
|
+
|
6
|
+
it "can't call Result#new directly" do
|
7
|
+
msg = "private method `new' called for FunctionalLightService::Result:Class"
|
8
|
+
expect { described_class.new(1) }.to raise_error(NoMethodError, msg)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "fmap" do
|
12
|
+
expect(Success(1).fmap { |n| n + 1 }).to eq Success(2)
|
13
|
+
expect(Failure(0).fmap { |n| n + 1 }).to eq Failure(1)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "map" do
|
17
|
+
expect(Success(1).map { |n| Success(n + 1) }).to eq Success(2)
|
18
|
+
expect(Failure(0).map { |n| Success(n + 1) }).to eq Failure(0)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "+" do
|
22
|
+
expect(Success([1]) + Failure([2])).to eq Failure([2])
|
23
|
+
expect(Success(1) + Success(1)).to eq Success(2)
|
24
|
+
expect(Failure(2) + Success(1)).to eq Failure(2)
|
25
|
+
expect(Failure([2]) + Failure([3]) + Success(1)).to eq Failure([2, 3])
|
26
|
+
expect(Success([1]) + Success([1])).to eq Success([1, 1])
|
27
|
+
expect { Success([1]) + Success(1) }.to raise_error TypeError
|
28
|
+
end
|
29
|
+
|
30
|
+
subject { Success(1) }
|
31
|
+
# specify { expect(subject).to be_an_instance_of described_class }
|
32
|
+
specify { expect(subject).to be_success }
|
33
|
+
specify { expect(subject).not_to be_failure }
|
34
|
+
specify { expect(subject.success?).to be_truthy }
|
35
|
+
specify { expect(subject.failure?).to be_falsey }
|
36
|
+
|
37
|
+
specify { expect(subject).to be_a described_class }
|
38
|
+
# specify { expect(subject).to eq(described_class.new(1)) }
|
39
|
+
specify { expect(subject.fmap { |v| v + 1 }).to eq Success(2) }
|
40
|
+
specify { expect(subject.map { |v| Failure(v + 1) }).to eq Failure(2) }
|
41
|
+
specify { expect(subject.map_err { |v| Failure(v + 1) }).to eq Success(1) }
|
42
|
+
|
43
|
+
specify do
|
44
|
+
expect(subject.pipe do |r|
|
45
|
+
raise RuntimeError unless r == Success(1)
|
46
|
+
end).to eq Success(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
specify { expect(subject.or(Success(2))).to eq Success(1) }
|
50
|
+
specify { expect(subject.or_else { Success(2) }).to eq Success(1) }
|
51
|
+
|
52
|
+
specify { expect(subject.and(Success(2))).to eq Success(2) }
|
53
|
+
specify { expect(subject.and(Failure(2))).to eq Failure(2) }
|
54
|
+
specify { expect(subject.and_then { Success(2) }).to eq Success(2) }
|
55
|
+
specify { expect(subject.and_then { Failure(2) }).to eq Failure(2) }
|
56
|
+
|
57
|
+
it "try!" do
|
58
|
+
expect(described_class.try! { 1 }).to eq Success(1)
|
59
|
+
expect(described_class.try! { raise "error" }.inspect).to \
|
60
|
+
eq Failure(RuntimeError.new("error")).inspect
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FunctionalLightService::Enum do
|
4
|
+
include FunctionalLightService
|
5
|
+
|
6
|
+
it "can't use value" do
|
7
|
+
expect do
|
8
|
+
InvalidEnum = FunctionalLightService.enum do
|
9
|
+
Unary(:value)
|
10
|
+
end
|
11
|
+
end .to raise_error ArgumentError
|
12
|
+
end
|
13
|
+
|
14
|
+
context "Nullary, Unary, Binary" do
|
15
|
+
MyEnym = FunctionalLightService.enum do
|
16
|
+
Nullary()
|
17
|
+
Unary(:a)
|
18
|
+
Binary(:a, :b)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can't instantiate parent" do
|
22
|
+
expect { MyEnym.new }.to \
|
23
|
+
raise_error NoMethodError, "private method `new' called for MyEnym:Class"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "Nullary" do
|
27
|
+
n = MyEnym.Nullary
|
28
|
+
|
29
|
+
expect(n).to be_a MyEnym
|
30
|
+
expect(n).to be_a MyEnym::Nullary
|
31
|
+
expect(n.name).to eq "Nullary"
|
32
|
+
expect { n.value }.to raise_error NoMethodError
|
33
|
+
expect(n.inspect).to eq "Nullary"
|
34
|
+
expect(n.to_s).to eq ""
|
35
|
+
expect(n.fmap {}).to eq n
|
36
|
+
end
|
37
|
+
|
38
|
+
it "Unary" do
|
39
|
+
u = MyEnym::Unary(1)
|
40
|
+
|
41
|
+
expect(u).to be_a MyEnym
|
42
|
+
expect(u).to be_a MyEnym::Unary
|
43
|
+
expect(u.name).to eq "Unary"
|
44
|
+
expect(u.a).to eq 1
|
45
|
+
expect(u.value).to eq 1
|
46
|
+
expect(u.inspect).to eq "Unary(1)"
|
47
|
+
expect(u.to_s).to eq "1"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "Binary" do
|
51
|
+
# hash
|
52
|
+
b = MyEnym::Binary(:a => 1, :b => 2)
|
53
|
+
expect(b).to be_a MyEnym
|
54
|
+
expect(b).to be_a MyEnym::Binary
|
55
|
+
expect(b.name).to eq "Binary"
|
56
|
+
expect(b.inspect).to eq "Binary(a: 1, b: 2)"
|
57
|
+
|
58
|
+
expect(b.a).to eq 1
|
59
|
+
expect(b.b).to eq 2
|
60
|
+
expect(b.value).to eq(:a => 1, :b => 2)
|
61
|
+
|
62
|
+
# values only
|
63
|
+
b = MyEnym::Binary(1, 2)
|
64
|
+
expect(b.value).to eq(:a => 1, :b => 2)
|
65
|
+
|
66
|
+
# other names are ok
|
67
|
+
b = MyEnym::Binary(:c => 1, :d => 2)
|
68
|
+
expect(b.value).to eq(:a => 1, :b => 2)
|
69
|
+
|
70
|
+
expect { MyEnym::Binary(1) }.to raise_error ArgumentError
|
71
|
+
end
|
72
|
+
|
73
|
+
it "generated enum" do
|
74
|
+
expect(MyEnym.variants.sort).to eq %i[Unary Binary Nullary].sort
|
75
|
+
expect(MyEnym.constants.sort.inspect).to eq %i[Matcher Unary Binary Nullary].sort.to_s
|
76
|
+
|
77
|
+
b = MyEnym::Binary(:a => 1, :b => 2)
|
78
|
+
|
79
|
+
res =
|
80
|
+
MyEnym.match(b) do
|
81
|
+
Nullary() { 0 }
|
82
|
+
Unary() { |a| a }
|
83
|
+
Binary() { |x, y| [x, y] }
|
84
|
+
end
|
85
|
+
|
86
|
+
expect(res).to eq [1, 2]
|
87
|
+
|
88
|
+
res =
|
89
|
+
b.match do
|
90
|
+
Nullary() { 0 }
|
91
|
+
Unary() { |a| a }
|
92
|
+
Binary() { |x, y| [x, y] }
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(res).to eq [1, 2]
|
96
|
+
|
97
|
+
expect do
|
98
|
+
b.match do
|
99
|
+
Nullary # Nullary is treated as a constant
|
100
|
+
end
|
101
|
+
end.to raise_error(NameError)
|
102
|
+
|
103
|
+
expect do
|
104
|
+
b.match do
|
105
|
+
Nullary()
|
106
|
+
Unary()
|
107
|
+
Binary()
|
108
|
+
end
|
109
|
+
end.to raise_error ArgumentError, "No block given to `Nullary`"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
describe FunctionalLightService::LocalizationAdapter do
|
5
|
+
let(:action_class) { TestDoubles::AnAction }
|
6
|
+
let(:adapter) { described_class.new }
|
7
|
+
|
8
|
+
describe "#failure" do
|
9
|
+
subject { adapter.failure(message_or_key, action_class) }
|
10
|
+
|
11
|
+
context "when provided a Symbol" do
|
12
|
+
let(:message_or_key) { :not_found }
|
13
|
+
|
14
|
+
it "translates the message" do
|
15
|
+
expected_scope = "test_doubles/an_action.light_service.failures"
|
16
|
+
|
17
|
+
expect(I18n).to receive(:t)
|
18
|
+
.with(message_or_key, :scope => expected_scope)
|
19
|
+
.and_return("message")
|
20
|
+
|
21
|
+
expect(subject).to eq("message")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "allows passing interpolation options to I18n layer" do
|
25
|
+
expect(I18n).to receive(:t)
|
26
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
27
|
+
.and_return("message")
|
28
|
+
|
29
|
+
subject = adapter.failure(message_or_key,
|
30
|
+
action_class,
|
31
|
+
:i18n_variable => "value")
|
32
|
+
|
33
|
+
expect(subject).to eq("message")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when provided a String" do
|
38
|
+
let(:message_or_key) { "action failed" }
|
39
|
+
|
40
|
+
it "returns the message" do
|
41
|
+
expect(subject).to eq(message_or_key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#success" do
|
47
|
+
subject { adapter.success(message_or_key, action_class) }
|
48
|
+
|
49
|
+
context "when provided a Symbol" do
|
50
|
+
let(:message_or_key) { :not_found }
|
51
|
+
|
52
|
+
it "translates the message" do
|
53
|
+
expected_scope = "test_doubles/an_action.light_service.successes"
|
54
|
+
|
55
|
+
expect(I18n).to receive(:t)
|
56
|
+
.with(message_or_key, :scope => expected_scope)
|
57
|
+
.and_return("message")
|
58
|
+
|
59
|
+
expect(subject).to eq("message")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "allows passing interpolation options to I18n layer" do
|
63
|
+
expect(I18n).to receive(:t)
|
64
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
65
|
+
.and_return("message")
|
66
|
+
|
67
|
+
subject = adapter.success(message_or_key,
|
68
|
+
action_class,
|
69
|
+
:i18n_variable => "value")
|
70
|
+
|
71
|
+
expect(subject).to eq("message")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when provided a String" do
|
76
|
+
let(:message_or_key) { "action failed" }
|
77
|
+
|
78
|
+
it "returns the message" do
|
79
|
+
expect(subject).to eq(message_or_key)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
describe FunctionalLightService::Organizer::WithReducer do
|
5
|
+
let(:context) { FunctionalLightService::Context.make }
|
6
|
+
let(:action1) { TestDoubles::NullAction }
|
7
|
+
let(:action2) { TestDoubles::NullAction.clone }
|
8
|
+
let(:actions) { [action1, action2] }
|
9
|
+
|
10
|
+
before { context.current_action = action2 }
|
11
|
+
|
12
|
+
it "reduces the provided actions" do
|
13
|
+
result = described_class.new.with(context).reduce(actions)
|
14
|
+
|
15
|
+
expect(result).to eq(context)
|
16
|
+
expect(result).to be_success
|
17
|
+
end
|
18
|
+
|
19
|
+
it "executes a handler around each action and continues reducing" do
|
20
|
+
expect(action1).to receive(:execute).with(context).and_return(context)
|
21
|
+
|
22
|
+
result = described_class.new.with(context)
|
23
|
+
.around_each(TestDoubles::AroundEachNullHandler)
|
24
|
+
.reduce([action1])
|
25
|
+
|
26
|
+
expect(result).to eq(context)
|
27
|
+
expect(result).to be_success
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when FailWithRollbackError is caught" do
|
31
|
+
it "reduces the rollback" do
|
32
|
+
expect(action1).to receive(:execute).with(context).and_return(context)
|
33
|
+
expect(action2).to receive(:execute).with(context) do
|
34
|
+
raise FunctionalLightService::FailWithRollbackError
|
35
|
+
end
|
36
|
+
expect(action1).to receive(:rollback).with(context).and_return(context)
|
37
|
+
expect(action2).to receive(:rollback).with(context).and_return(context)
|
38
|
+
|
39
|
+
result = described_class.new.with(context).reduce(actions)
|
40
|
+
|
41
|
+
expect(result).to eq(context)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "reduces the rollback with an action without `rollback`" do
|
45
|
+
expect(action1).to receive(:execute).with(context).and_return(context)
|
46
|
+
expect(action2).to receive(:execute).with(context) do
|
47
|
+
raise FunctionalLightService::FailWithRollbackError
|
48
|
+
end
|
49
|
+
expect(action2).to receive(:rollback).with(context).and_return(context)
|
50
|
+
|
51
|
+
result = described_class.new.with(context).reduce(actions)
|
52
|
+
|
53
|
+
expect(result).to eq(context)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
describe "organizer aliases macro" do
|
5
|
+
let(:organizer_with_alias) do
|
6
|
+
Class.new do
|
7
|
+
extend FunctionalLightService::Organizer
|
8
|
+
|
9
|
+
aliases :promised_key => :expected_key
|
10
|
+
|
11
|
+
def self.call(ctx = {})
|
12
|
+
with(ctx).reduce(
|
13
|
+
[
|
14
|
+
TestDoubles::PromisesPromisedKeyAction,
|
15
|
+
TestDoubles::ExpectsExpectedKeyAction
|
16
|
+
]
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when aliases is invoked" do
|
23
|
+
it "makes aliases available to the actions" do
|
24
|
+
result = organizer_with_alias.call
|
25
|
+
expect(result[:expected_key]).to eq(result[:promised_key])
|
26
|
+
expect(result.expected_key).to eq(result[:promised_key])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
describe FunctionalLightService::Organizer do
|
5
|
+
let(:ctx) { FunctionalLightService::Context.make(:user => user) }
|
6
|
+
let(:user) { double(:user) }
|
7
|
+
|
8
|
+
context "when #with is called with hash" do
|
9
|
+
before do
|
10
|
+
expect(TestDoubles::AnAction).to receive(:execute)
|
11
|
+
.with(ctx)
|
12
|
+
.and_return(ctx)
|
13
|
+
expect(TestDoubles::AnotherAction).to receive(:execute)
|
14
|
+
.with(ctx)
|
15
|
+
.and_return(ctx)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "implicitly creates a Context" do
|
19
|
+
result = TestDoubles::AnOrganizer.call(:user => user)
|
20
|
+
expect(result).to eq(ctx)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when #with is called with Context" do
|
25
|
+
before do
|
26
|
+
expect(TestDoubles::AnAction).to receive(:execute)
|
27
|
+
.with(ctx)
|
28
|
+
.and_return(ctx)
|
29
|
+
expect(TestDoubles::AnotherAction).to receive(:execute)
|
30
|
+
.with(ctx)
|
31
|
+
.and_return(ctx)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "uses that Context without recreating it" do
|
35
|
+
result = TestDoubles::AnOrganizer.call(ctx)
|
36
|
+
expect(result).to eq(ctx)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when no Actions are specified" do
|
41
|
+
it "throws a Runtime error" do
|
42
|
+
expect { TestDoubles::AnOrganizer.do_something_with_no_actions(ctx) }.to \
|
43
|
+
raise_error(RuntimeError, "No action(s) were provided")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when aliases are declared" do
|
48
|
+
let(:organizer) do
|
49
|
+
Class.new do
|
50
|
+
extend FunctionalLightService::Organizer
|
51
|
+
aliases :foo => :bar
|
52
|
+
|
53
|
+
def self.call
|
54
|
+
with.reduce(TestDoubles::AnAction)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "merges the aliases into the data" do
|
60
|
+
with_reducer = double(:reduce => true)
|
61
|
+
|
62
|
+
allow(described_class::WithReducerFactory).to receive(:make)
|
63
|
+
.and_return(with_reducer)
|
64
|
+
|
65
|
+
expect(with_reducer).to receive(:with)
|
66
|
+
.with(hash_including(:_aliases => { :foo => :bar }))
|
67
|
+
.and_return(with_reducer)
|
68
|
+
|
69
|
+
organizer.call
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when an organizer is nested and reduced within another" do
|
74
|
+
let(:reduced) { TestDoubles::NestingOrganizer.call(ctx) }
|
75
|
+
let(:organizer_result) do
|
76
|
+
TestDoubles::NotExplicitlyReturningContextOrganizer.call(ctx)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "reduces an organizer which returns something" do
|
80
|
+
expect(organizer_result).to eq([1, 2, 3])
|
81
|
+
end
|
82
|
+
|
83
|
+
it "adds :foo and :bar to the context" do
|
84
|
+
reduced
|
85
|
+
expect(ctx[:foo]).to eq([1, 2, 3])
|
86
|
+
expect(ctx[:bar]).to eq(ctx[:foo])
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns the context" do
|
90
|
+
expect(reduced).to eq(ctx)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/readme_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# rubocop:disable Style/MixinUsage
|
4
|
+
include FunctionalLightService::Prelude::Result
|
5
|
+
# rubocop:enable Style/MixinUsage
|
6
|
+
|
7
|
+
Success(1).to_s # => "1"
|
8
|
+
Success(Success(1)) # => Success(1)
|
9
|
+
|
10
|
+
Failure(1).to_s # => "1"
|
11
|
+
Failure(Failure(1)) # => Failure(1)
|
12
|
+
|
13
|
+
Success(1).fmap { |v| v + 1 } # => Success(2)
|
14
|
+
Failure(1).fmap { |v| v - 1 } # => Failure(0)
|
15
|
+
|
16
|
+
Threenum = FunctionalLightService.enum do
|
17
|
+
Nullary()
|
18
|
+
Unary(:a)
|
19
|
+
Binary(:a, :b)
|
20
|
+
end
|
21
|
+
|
22
|
+
FunctionalLightService.impl(Threenum) do
|
23
|
+
def sum
|
24
|
+
match do
|
25
|
+
Nullary() { 0 }
|
26
|
+
Unary() { |u| u }
|
27
|
+
Binary() { |a, b| a + b }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def +(other)
|
32
|
+
match do
|
33
|
+
Nullary() { other.sum }
|
34
|
+
Unary() { |_a| sum + other.sum }
|
35
|
+
Binary() { |_a, _b| sum + other.sum }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Threenum do
|
41
|
+
it "works" do
|
42
|
+
expect(Threenum.Nullary + Threenum.Unary(1)).to eq 1
|
43
|
+
expect(Threenum.Nullary + Threenum.Binary(2, 3)).to eq 5
|
44
|
+
expect(Threenum.Unary(1) + Threenum.Binary(2, 3)).to eq 6
|
45
|
+
expect(Threenum.Binary(2, 3) + Threenum.Binary(2, 3)).to eq 10
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'tax/calculates_order_tax_action'
|
3
|
+
|
4
|
+
describe CalculatesOrderTaxAction do
|
5
|
+
let(:order) { double('order') }
|
6
|
+
let(:context) do
|
7
|
+
data = { :order => order, :tax_percentage => 7.2 }
|
8
|
+
::FunctionalLightService::Context.make(data)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "calculates the tax based on the tax percentage" do
|
12
|
+
allow(order).to receive_messages(:total => 100)
|
13
|
+
expect(order).to receive(:tax=).with 7.2
|
14
|
+
CalculatesOrderTaxAction.execute(context)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'tax/calculates_tax'
|
3
|
+
require_relative 'tax/looks_up_tax_percentage_action'
|
4
|
+
require_relative 'tax/calculates_order_tax_action'
|
5
|
+
require_relative 'tax/provides_free_shipping_action'
|
6
|
+
|
7
|
+
describe CalculatesTax do
|
8
|
+
let(:order) { double('order') }
|
9
|
+
let(:ctx) { FunctionalLightService::Context.make(:user => nil) }
|
10
|
+
|
11
|
+
it "calls the actions in order" do
|
12
|
+
allow(FunctionalLightService::Context).to receive(:make)
|
13
|
+
.with(:order => order)
|
14
|
+
.and_return(ctx)
|
15
|
+
|
16
|
+
allow(LooksUpTaxPercentageAction).to receive(:execute)
|
17
|
+
.with(ctx)
|
18
|
+
.and_return(ctx)
|
19
|
+
allow(CalculatesOrderTaxAction).to receive(:execute)
|
20
|
+
.with(ctx)
|
21
|
+
.and_return(ctx)
|
22
|
+
allow(ProvidesFreeShippingAction).to receive(:execute)
|
23
|
+
.with(ctx)
|
24
|
+
.and_return(ctx)
|
25
|
+
|
26
|
+
result = CalculatesTax.call(order)
|
27
|
+
|
28
|
+
expect(result).to eq(ctx)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'tax/looks_up_tax_percentage_action'
|
3
|
+
|
4
|
+
class TaxRange; end
|
5
|
+
|
6
|
+
describe LooksUpTaxPercentageAction do
|
7
|
+
let(:region) { double('region') }
|
8
|
+
let(:order) do
|
9
|
+
order = double('order')
|
10
|
+
allow(order).to receive_messages(:region => region)
|
11
|
+
allow(order).to receive_messages(:total => 200)
|
12
|
+
order
|
13
|
+
end
|
14
|
+
let(:context) do
|
15
|
+
::FunctionalLightService::Context.make(:order => order)
|
16
|
+
end
|
17
|
+
let(:tax_percentage) { double('tax_percentage') }
|
18
|
+
let(:tax_ranges) { double('tax_ranges') }
|
19
|
+
|
20
|
+
context "when the tax_ranges were not found" do
|
21
|
+
it "sets the context to failure" do
|
22
|
+
allow(TaxRange).to receive(:for_region).with(region).and_return nil
|
23
|
+
LooksUpTaxPercentageAction.execute(context)
|
24
|
+
|
25
|
+
expect(context).to be_failure
|
26
|
+
expect(context.message).to eq "The tax ranges were not found"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the tax_percentage is not found" do
|
31
|
+
it "sets the context to failure" do
|
32
|
+
allow(TaxRange).to receive(:for_region).with(region).and_return tax_ranges
|
33
|
+
allow(tax_ranges).to receive_messages(:for_total => nil)
|
34
|
+
|
35
|
+
LooksUpTaxPercentageAction.execute(context)
|
36
|
+
|
37
|
+
expect(context).to be_failure
|
38
|
+
expect(context.message).to eq "The tax percentage was not found"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when the tax_percentage is found" do
|
43
|
+
it "sets the tax_percentage in context" do
|
44
|
+
allow(TaxRange).to receive(:for_region).with(region).and_return tax_ranges
|
45
|
+
allow(tax_ranges).to receive_messages(:for_total => 25)
|
46
|
+
|
47
|
+
LooksUpTaxPercentageAction.execute(context)
|
48
|
+
|
49
|
+
expect(context).to be_success
|
50
|
+
expect(context.fetch(:tax_percentage)).to eq 25
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'tax/provides_free_shipping_action'
|
3
|
+
|
4
|
+
describe ProvidesFreeShippingAction do
|
5
|
+
let(:order) { double('order') }
|
6
|
+
let(:ctx) { { :order => order } }
|
7
|
+
|
8
|
+
context "when the order total with tax is > 200" do
|
9
|
+
specify "order gets free shipping" do
|
10
|
+
allow(order).to receive_messages(:total_with_tax => 201)
|
11
|
+
expect(order).to receive(:provide_free_shipping!)
|
12
|
+
|
13
|
+
ProvidesFreeShippingAction.execute(ctx)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when the order total with tax is <= 200" do
|
18
|
+
specify "order gets free shipping" do
|
19
|
+
allow(order).to receive_messages(:total_with_tax => 200)
|
20
|
+
expect(order).not_to receive(:provide_free_shipping!)
|
21
|
+
|
22
|
+
ProvidesFreeShippingAction.execute(ctx)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class LooksUpTaxPercentageAction
|
2
|
+
extend FunctionalLightService::Action
|
3
|
+
expects :order
|
4
|
+
promises :tax_percentage
|
5
|
+
|
6
|
+
executed do |ctx|
|
7
|
+
tax_ranges = TaxRange.for_region(ctx.order.region)
|
8
|
+
ctx.tax_percentage = 0
|
9
|
+
|
10
|
+
next ctx if object_is_nil?(tax_ranges, ctx, 'The tax ranges were not found')
|
11
|
+
|
12
|
+
ctx.tax_percentage = tax_ranges.for_total(ctx.order.total)
|
13
|
+
|
14
|
+
error_message = 'The tax percentage was not found'
|
15
|
+
next ctx if object_is_nil?(ctx.tax_percentage, ctx, error_message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.object_is_nil?(object, ctx, message)
|
19
|
+
if object.nil?
|
20
|
+
ctx.fail!(message)
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
false
|
25
|
+
end
|
26
|
+
private_class_method :object_is_nil?
|
27
|
+
end
|