deterministic 0.14.1 → 0.15.0
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 +4 -4
- data/README.md +108 -41
- data/lib/deterministic.rb +1 -0
- data/lib/deterministic/enum.rb +203 -0
- data/lib/deterministic/monad.rb +19 -5
- data/lib/deterministic/option.rb +40 -80
- data/lib/deterministic/protocol.rb +103 -0
- data/lib/deterministic/result.rb +49 -91
- data/lib/deterministic/version.rb +1 -1
- data/spec/examples/amount_spec.rb +67 -0
- data/spec/examples/config_spec.rb +5 -2
- data/spec/examples/controller_spec.rb +1 -1
- data/spec/examples/list.rb +183 -0
- data/spec/examples/list_spec.rb +186 -0
- data/spec/examples/logger_spec.rb +13 -8
- data/spec/examples/validate_address_spec.rb +5 -4
- data/spec/lib/deterministic/class_mixin_spec.rb +3 -4
- data/spec/lib/deterministic/core_ext/result_spec.rb +4 -2
- data/spec/lib/deterministic/currify_spec.rb +88 -0
- data/spec/lib/deterministic/monad_spec.rb +3 -3
- data/spec/lib/deterministic/option_spec.rb +107 -98
- data/spec/lib/deterministic/protocol_spec.rb +43 -0
- data/spec/lib/deterministic/result/failure_spec.rb +1 -7
- data/spec/lib/deterministic/result/{result_map.rb → result_map_spec.rb} +9 -9
- data/spec/lib/deterministic/result/success_spec.rb +1 -2
- data/spec/lib/deterministic/result_spec.rb +44 -15
- data/spec/lib/enum_spec.rb +108 -0
- data/spec/spec_helper.rb +2 -1
- metadata +17 -8
- data/.rspec +0 -2
- data/spec/examples/bookings_spec.rb +0 -72
- data/spec/lib/deterministic/result/match_spec.rb +0 -116
@@ -17,7 +17,7 @@ class Logger
|
|
17
17
|
def validate(item)
|
18
18
|
return Failure(["Item cannot be empty"]) if item.blank?
|
19
19
|
return Failure(["Item must be a Hash"]) unless item.is_a?(Hash)
|
20
|
-
|
20
|
+
|
21
21
|
validate_required_params(item).match {
|
22
22
|
none { Success(item) }
|
23
23
|
some { |errors| Failure(errors) }
|
@@ -46,7 +46,11 @@ class Logger
|
|
46
46
|
class Ensure
|
47
47
|
include Deterministic
|
48
48
|
include Deterministic::Monad
|
49
|
-
|
49
|
+
|
50
|
+
None = Deterministic::Option::None.new
|
51
|
+
def Some(value)
|
52
|
+
Option::Some.new(value)
|
53
|
+
end
|
50
54
|
|
51
55
|
attr_accessor :value
|
52
56
|
|
@@ -81,34 +85,35 @@ class Ensure
|
|
81
85
|
end
|
82
86
|
|
83
87
|
class Validator < Ensure
|
84
|
-
|
88
|
+
|
85
89
|
def date_is_one!
|
86
90
|
value[:date] == 1 ? None : Some({actual: value[:date], expected: 1})
|
87
91
|
end
|
88
92
|
|
89
93
|
def required_params!
|
90
94
|
params = %w(date tenant contract user facility short data)
|
91
|
-
params.inject(None) { |errors, param|
|
95
|
+
params.inject(None) { |errors, param|
|
92
96
|
errors + (value[:param].nil? || value[:param].empty? ? Some([param]) : None)
|
93
97
|
}
|
94
98
|
end
|
95
99
|
|
96
100
|
def call
|
97
|
-
not_empty + is_a(Array) + None + has_key(:tenant) + Some("error")
|
101
|
+
not_empty + is_a(Array) + None + has_key(:tenant) + Some(["error"]) #+ date_is_one + required_params
|
98
102
|
end
|
99
103
|
|
100
104
|
end
|
101
105
|
|
102
106
|
describe Ensure do
|
103
|
-
|
104
|
-
|
107
|
+
None = Deterministic::Option::None.new
|
108
|
+
Some = Deterministic::Option::Some
|
109
|
+
|
105
110
|
it "Ensure" do
|
106
111
|
params = {date: 2}
|
107
112
|
|
108
113
|
v = Validator.new(params)
|
109
114
|
|
110
115
|
errors = v.call
|
111
|
-
expect(errors).to be_a
|
116
|
+
expect(errors).to be_a Some
|
112
117
|
expect(errors.value).not_to be_empty
|
113
118
|
end
|
114
119
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'deterministic'
|
3
2
|
|
4
|
-
include Deterministic
|
5
3
|
|
6
4
|
# A Unit of Work for validating an address
|
7
5
|
module ValidateAddress
|
6
|
+
extend Deterministic::Prelude::Result
|
7
|
+
|
8
8
|
def self.call(candidate)
|
9
9
|
errors = {}
|
10
10
|
errors[:street] = "Street cannot be empty" unless candidate.has_key? :street
|
@@ -16,16 +16,17 @@ module ValidateAddress
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe ValidateAddress do
|
19
|
+
include Deterministic
|
19
20
|
subject { ValidateAddress.call(candidate) }
|
20
21
|
context 'sunny day' do
|
21
22
|
let(:candidate) { {title: "Hobbiton", street: "501 Buckland Rd", city: "Matamata", postal: "3472", country: "nz"} }
|
22
|
-
specify { expect(subject).to be_a Result::Success }
|
23
|
+
specify { expect(subject).to be_a Deterministic::Result::Success }
|
23
24
|
specify { expect(subject.value).to eq candidate }
|
24
25
|
end
|
25
26
|
|
26
27
|
context 'empty data' do
|
27
28
|
let(:candidate) { {} }
|
28
|
-
specify { expect(subject).to be_a Result::Failure }
|
29
|
+
specify { expect(subject).to be_a Deterministic::Result::Failure }
|
29
30
|
specify { expect(subject.value).to include(:street, :city, :postal) }
|
30
31
|
end
|
31
32
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Class Mixin' do
|
4
|
-
|
5
4
|
describe 'try' do
|
6
5
|
module MyApp
|
7
6
|
class Thing
|
8
|
-
include Deterministic
|
7
|
+
include Deterministic::Prelude::Result
|
9
8
|
|
10
9
|
def run
|
11
|
-
Success(11)
|
10
|
+
Success(11) >> method(:double)
|
12
11
|
end
|
13
12
|
|
14
13
|
def double(num)
|
@@ -19,7 +18,7 @@ describe 'Class Mixin' do
|
|
19
18
|
|
20
19
|
it "cleanly mixes into a class" do
|
21
20
|
result = MyApp::Thing.new.run
|
22
|
-
expect(result).to eq Deterministic::Success.new(22)
|
21
|
+
expect(result).to eq Deterministic::Result::Success.new(22)
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -3,6 +3,8 @@ require "deterministic"
|
|
3
3
|
require "deterministic/core_ext/result"
|
4
4
|
|
5
5
|
describe Deterministic::CoreExt::Result do
|
6
|
+
include Deterministic::Prelude::Result
|
7
|
+
|
6
8
|
it "does something" do
|
7
9
|
h = {}
|
8
10
|
h.extend(Deterministic::CoreExt::Result)
|
@@ -12,10 +14,10 @@ describe Deterministic::CoreExt::Result do
|
|
12
14
|
end
|
13
15
|
|
14
16
|
it "enables #success?, #failure?, #result? on all Objects" do
|
15
|
-
ary = [
|
17
|
+
ary = [Success(true), Success(1)]
|
16
18
|
expect(ary.all?(&:success?)).to be_truthy
|
17
19
|
|
18
|
-
ary = [
|
20
|
+
ary = [Success(true), Failure(1)]
|
19
21
|
expect(ary.all?(&:success?)).to be_falsey
|
20
22
|
expect(ary.any?(&:failure?)).to be_truthy
|
21
23
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Deterministic
|
4
|
+
module Currify
|
5
|
+
module ClassMethods
|
6
|
+
def currify(*names)
|
7
|
+
names.each { |name|
|
8
|
+
unbound_method = instance_method(name)
|
9
|
+
|
10
|
+
define_method(name) { |*args|
|
11
|
+
curried_method = unbound_method.bind(self).to_proc.curry
|
12
|
+
curried_method[*args]
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(curried)
|
19
|
+
curried.extend ClassMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::Proc
|
26
|
+
def self.compose(f, g)
|
27
|
+
lambda { |*args| f[g[*args]] }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Compose left to right
|
31
|
+
def |(g)
|
32
|
+
Proc.compose(g, self)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compose right to left
|
36
|
+
def *(g)
|
37
|
+
Proc.compose(self, g)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Booking
|
42
|
+
include Deterministic::Currify
|
43
|
+
include Deterministic::Prelude::Result
|
44
|
+
|
45
|
+
def initialize(deps)
|
46
|
+
@deps = deps
|
47
|
+
end
|
48
|
+
|
49
|
+
def build(id, format)
|
50
|
+
validate(id) | req | find | render(format)
|
51
|
+
|
52
|
+
validate(id) | rq = request(id) | find()
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate(id)
|
56
|
+
p [:validate, id]
|
57
|
+
Success(id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def req(a, id)
|
61
|
+
p [:req, a, id]
|
62
|
+
Success(id: id + a)
|
63
|
+
end
|
64
|
+
|
65
|
+
def find(req)
|
66
|
+
Success({ found: req})
|
67
|
+
end
|
68
|
+
|
69
|
+
def render(format, req)
|
70
|
+
Success("rendered in #{format}: #{req[:found]}")
|
71
|
+
end
|
72
|
+
|
73
|
+
currify :find, :render, :req
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "Pref" do
|
78
|
+
include Deterministic::Prelude::Result
|
79
|
+
|
80
|
+
it "does something" do
|
81
|
+
b = Booking.new(1)
|
82
|
+
actual = b.validate(1) >> b.req(2) >> b.find >> b.render(:html)
|
83
|
+
|
84
|
+
p [:actual, actual]
|
85
|
+
expected = Deterministic::Result::Success.new("rendered in html: {:id=>3}")
|
86
|
+
expect(actual).to eq expected
|
87
|
+
end
|
88
|
+
end
|
@@ -3,12 +3,12 @@ require_relative 'monad_axioms'
|
|
3
3
|
|
4
4
|
|
5
5
|
describe Deterministic::Monad do
|
6
|
-
class Identity
|
6
|
+
class Identity
|
7
7
|
include Deterministic::Monad
|
8
8
|
end
|
9
9
|
|
10
10
|
let(:monad) { Identity }
|
11
|
-
it_behaves_like 'a Monad' do
|
11
|
+
it_behaves_like 'a Monad' do
|
12
12
|
# let(:monad) { monad }
|
13
13
|
end
|
14
14
|
|
@@ -21,7 +21,7 @@ describe Deterministic::Monad do
|
|
21
21
|
specify { expect(Identity.new('foo').fmap(&:upcase)).to eq Identity.new('FOO')}
|
22
22
|
|
23
23
|
context '#bind' do
|
24
|
-
it "raises an error if the passed function does not return a monad of the same class" do
|
24
|
+
it "raises an error if the passed function does not return a monad of the same class" do
|
25
25
|
expect { Identity.new(1).bind {} }.to raise_error(Deterministic::Monad::NotMonadError)
|
26
26
|
end
|
27
27
|
specify { expect(Identity.new(1).bind {|value| Identity.new(value) }).to eq Identity.new(1) }
|
@@ -1,131 +1,140 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require_relative 'monad_axioms'
|
3
2
|
|
3
|
+
include Deterministic
|
4
4
|
|
5
5
|
describe Deterministic::Option do
|
6
|
-
include Deterministic
|
7
|
-
|
8
|
-
None = Deterministic::None
|
9
|
-
|
10
|
-
# nil?
|
11
|
-
specify { expect(described_class.some?(nil)).to eq None }
|
12
|
-
specify { expect(described_class.some?(1)).to be_some }
|
13
|
-
specify { expect(described_class.some?(1)).to eq Some(1) }
|
14
|
-
|
15
|
-
# any?
|
16
|
-
specify { expect(described_class.any?(nil)).to be_none }
|
17
|
-
specify { expect(described_class.any?(None)).to be_none }
|
18
|
-
specify { expect(described_class.any?("")).to be_none }
|
19
|
-
specify { expect(described_class.any?([])).to be_none }
|
20
|
-
specify { expect(described_class.any?({})).to be_none }
|
21
|
-
specify { expect(described_class.any?([1])).to eq Some([1]) }
|
22
|
-
specify { expect(described_class.any?({foo: 1})).to eq Some({foo: 1}) }
|
23
|
-
|
24
|
-
# try!
|
25
|
-
specify { expect(described_class.try! { raise "error" }).to be_none }
|
26
|
-
end
|
6
|
+
include Deterministic::Prelude::Option
|
7
|
+
None = Deterministic::Prelude::Option::None
|
27
8
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
9
|
+
specify { expect(described_class::Some.new(0)).to be_a described_class::Some }
|
10
|
+
specify { expect(described_class::Some.new(0)).to be_a described_class }
|
11
|
+
specify { expect(described_class::Some.new(0)).to eq Some(0) }
|
12
|
+
|
13
|
+
specify { expect(described_class::None.new).to eq described_class::None.new }
|
14
|
+
specify { expect(described_class::None.new).to be_a described_class::None }
|
15
|
+
specify { expect(described_class::None.new).to be_a described_class }
|
16
|
+
specify { expect(described_class::None.new).to eq None }
|
17
|
+
|
18
|
+
it "join" do
|
19
|
+
expect(Some(Some(1))).to eq Some(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "fmap" do
|
23
|
+
expect(Some(1).fmap { |n| n + 1}).to eq Some(2)
|
24
|
+
expect(None.fmap { |n| n + 1}).to eq None
|
25
|
+
end
|
26
|
+
|
27
|
+
it "map" do
|
28
|
+
expect(Some(1).map { |n| Some(n + 1)}).to eq Some(2)
|
29
|
+
expect(Some(1).map { |n| None }).to eq None
|
30
|
+
expect(None.map { |n| Some(n + 1)}).to eq None
|
31
|
+
end
|
32
|
+
|
33
|
+
it "some?" do
|
34
|
+
expect(Some(1).some?).to be_truthy
|
35
|
+
expect(None.some?).to be_falsey
|
36
|
+
end
|
37
|
+
|
38
|
+
it "none?" do
|
39
|
+
expect(None.none?).to be_truthy
|
40
|
+
expect(Some(1).none?).to be_falsey
|
41
|
+
end
|
42
|
+
|
43
|
+
it "value" do
|
44
|
+
expect(Some(1).value).to eq 1
|
45
|
+
expect{ None.value }.to raise_error NoMethodError
|
46
|
+
end
|
47
|
+
|
48
|
+
it "value_or" do
|
49
|
+
expect(Some(1).value_or(2)).to eq 1
|
50
|
+
expect(None.value_or(0)).to eq 0
|
51
|
+
end
|
52
|
+
|
53
|
+
it "+" do
|
54
|
+
expect(Some([1]) + None).to eq Some([1])
|
55
|
+
expect(Some(1) + None + None).to eq Some(1)
|
56
|
+
expect(Some(1) + Some(1)).to eq Some(2)
|
57
|
+
expect(None + Some(1)).to eq Some(1)
|
58
|
+
expect(None + None + Some(1)).to eq Some(1)
|
59
|
+
expect(None + None + Some(1) + None).to eq Some(1)
|
60
|
+
expect(None + Some({foo: 1})).to eq Some({:foo=>1})
|
61
|
+
expect(Some([1]) + Some([1])).to eq Some([1, 1])
|
62
|
+
expect { Some([1]) + Some(1)}.to raise_error TypeError
|
63
|
+
end
|
64
|
+
|
65
|
+
it "inspect" do
|
66
|
+
expect(Some(1).inspect).to eq "Some(1)"
|
67
|
+
expect(described_class::None.new.inspect).to eq "None"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "to_s" do
|
71
|
+
expect(Some(1).to_s).to eq "1"
|
72
|
+
expect(described_class::None.new.to_s).to eq ""
|
73
|
+
end
|
74
|
+
|
75
|
+
it "match" do
|
79
76
|
expect(
|
80
77
|
Some(0).match {
|
81
|
-
|
82
|
-
|
83
|
-
|
78
|
+
Some(s, where { s == 1 } ) { |n| 99 }
|
79
|
+
Some(s, where { s == 0 }) { |n| s + 1 }
|
80
|
+
None() {}
|
84
81
|
}
|
85
82
|
).to eq 1
|
86
|
-
}
|
87
83
|
|
88
|
-
specify {
|
89
84
|
expect(
|
90
|
-
Some(
|
91
|
-
|
92
|
-
|
85
|
+
Some(1).match {
|
86
|
+
None() { 0 }
|
87
|
+
Some(s) { 1 }
|
93
88
|
}
|
94
89
|
).to eq 1
|
95
|
-
}
|
96
90
|
|
97
|
-
specify {
|
98
91
|
expect(
|
99
92
|
Some(1).match {
|
100
|
-
|
101
|
-
|
93
|
+
None() { 0 }
|
94
|
+
Some(s, where { s.is_a? Fixnum }) { 1 }
|
102
95
|
}
|
103
96
|
).to eq 1
|
104
|
-
}
|
105
97
|
|
106
|
-
specify {
|
107
98
|
expect(
|
108
99
|
None.match {
|
109
|
-
|
110
|
-
|
100
|
+
None() { 0 }
|
101
|
+
Some() { 1 }
|
111
102
|
}
|
112
103
|
).to eq 0
|
113
|
-
|
104
|
+
end
|
114
105
|
|
115
|
-
|
106
|
+
it "nil?" do
|
107
|
+
expect(described_class.some?(nil)).to eq None
|
108
|
+
expect(described_class.some?(1)).to be_some
|
109
|
+
expect(described_class.some?(1)).to eq Some(1)
|
110
|
+
end
|
116
111
|
|
117
|
-
|
118
|
-
|
112
|
+
it "any?" do
|
113
|
+
expect(described_class.any?(nil)).to be_none
|
114
|
+
expect(described_class.any?(None)).to be_none
|
115
|
+
expect(described_class.any?("")).to be_none
|
116
|
+
expect(described_class.any?([])).to be_none
|
117
|
+
expect(described_class.any?({})).to be_none
|
118
|
+
expect(described_class.any?([1])).to eq Some([1])
|
119
|
+
expect(described_class.any?({foo: 1})).to eq Some({foo: 1})
|
120
|
+
end
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
+
it "try!" do
|
123
|
+
expect(described_class.try! { raise "error" }).to be_none
|
122
124
|
end
|
123
125
|
end
|
124
126
|
|
125
|
-
|
126
|
-
include Deterministic
|
127
|
+
require_relative 'monad_axioms'
|
127
128
|
|
128
|
-
|
129
|
+
describe Deterministic::Option::Some do
|
130
|
+
it_behaves_like 'a Monad' do
|
129
131
|
let(:monad) { described_class }
|
130
132
|
end
|
131
133
|
end
|
134
|
+
|
135
|
+
# describe Deterministic::Option::None do
|
136
|
+
# it_behaves_like 'a Monad' do
|
137
|
+
|
138
|
+
# let(:monad) { described_class }
|
139
|
+
# end
|
140
|
+
# end
|