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