functional-ruby 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +37 -17
- data/doc/record.txt +63 -0
- data/doc/thread_safety.txt +9 -0
- data/lib/functional.rb +4 -0
- data/lib/functional/abstract_struct.rb +4 -2
- data/lib/functional/delay.rb +2 -0
- data/lib/functional/either.rb +2 -0
- data/lib/functional/final_struct.rb +231 -0
- data/lib/functional/final_var.rb +163 -0
- data/lib/functional/memo.rb +4 -0
- data/lib/functional/method_signature.rb +2 -0
- data/lib/functional/option.rb +2 -0
- data/lib/functional/pattern_matching.rb +1 -0
- data/lib/functional/protocol.rb +2 -0
- data/lib/functional/protocol_info.rb +3 -0
- data/lib/functional/record.rb +4 -2
- data/lib/functional/tuple.rb +247 -0
- data/lib/functional/type_check.rb +2 -0
- data/lib/functional/union.rb +2 -0
- data/lib/functional/value_struct.rb +144 -0
- data/lib/functional/version.rb +1 -1
- data/spec/functional/final_struct_spec.rb +266 -0
- data/spec/functional/final_var_spec.rb +169 -0
- data/spec/functional/record_spec.rb +30 -1
- data/spec/functional/tuple_spec.rb +679 -0
- data/spec/functional/value_struct_spec.rb +199 -0
- metadata +19 -4
@@ -3,6 +3,8 @@ module Functional
|
|
3
3
|
# Supplies type-checking helpers whenever included.
|
4
4
|
#
|
5
5
|
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor/TypeCheck.html TypeCheck in Concurrent Ruby
|
6
|
+
#
|
7
|
+
# @since 1.0.0
|
6
8
|
module TypeCheck
|
7
9
|
|
8
10
|
# Performs an `is_a?` check of the given value object against the
|
data/lib/functional/union.rb
CHANGED
@@ -52,6 +52,8 @@ module Functional
|
|
52
52
|
# @see http://www.ruby-doc.org/core-2.1.2/Struct.html Ruby `Struct` class
|
53
53
|
# @see http://en.wikipedia.org/wiki/Union_type "Union type" on Wikipedia
|
54
54
|
#
|
55
|
+
# @since 1.0.0
|
56
|
+
#
|
55
57
|
# @!macro thread_safe_immutable_object
|
56
58
|
module Union
|
57
59
|
extend self
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Functional
|
2
|
+
|
3
|
+
# A variation on Ruby's `OpenStruct` in which all fields are immutable and
|
4
|
+
# set at instantiation. For compatibility with {Functional::FinalStruct},
|
5
|
+
# predicate methods exist for all potential fields and these predicates
|
6
|
+
# indicate if the field has been set. Calling a predicate method for a field
|
7
|
+
# that does not exist on the struct will return false.
|
8
|
+
#
|
9
|
+
# Unlike {Functional::Record}, which returns a new class which can be used to
|
10
|
+
# create immutable objects, `ValueStruct` creates simple immutable objects.
|
11
|
+
#
|
12
|
+
# @example Instanciation
|
13
|
+
# name = Functional::ValueStruct.new(first: 'Douglas', last: 'Adams')
|
14
|
+
#
|
15
|
+
# name.first #=> 'Douglas'
|
16
|
+
# name.last #=> 'Adams'
|
17
|
+
# name.first? #=> true
|
18
|
+
# name.last? #=> true
|
19
|
+
# name.middle? #=> false
|
20
|
+
#
|
21
|
+
# @since 1.1.0
|
22
|
+
#
|
23
|
+
# @see Functional::Record
|
24
|
+
# @see Functional::FinalStruct
|
25
|
+
# @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/ostruct/rdoc/OpenStruct.html
|
26
|
+
#
|
27
|
+
# @!macro thread_safe_immutable_object
|
28
|
+
class ValueStruct
|
29
|
+
|
30
|
+
def initialize(attributes)
|
31
|
+
raise ArgumentError.new('attributes must be given as a hash') unless attributes.respond_to?(:each_pair)
|
32
|
+
@attribute_hash = {}
|
33
|
+
attributes.each_pair do |field, value|
|
34
|
+
set_attribute(field, value)
|
35
|
+
end
|
36
|
+
@attribute_hash.freeze
|
37
|
+
self.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the value of the given field.
|
41
|
+
#
|
42
|
+
# @param [Symbol] field the field to retrieve the value for
|
43
|
+
# @return [Object] the value of the field is set else nil
|
44
|
+
def get(field)
|
45
|
+
@attribute_hash[field.to_sym]
|
46
|
+
end
|
47
|
+
alias_method :[], :get
|
48
|
+
|
49
|
+
# Check the internal hash to unambiguously verify that the given
|
50
|
+
# attribute has been set.
|
51
|
+
#
|
52
|
+
# @param [Symbol] field the field to get the value for
|
53
|
+
# @return [Boolean] true if the field has been set else false
|
54
|
+
def set?(field)
|
55
|
+
@attribute_hash.has_key?(field.to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get the current value of the given field if already set else return the given
|
59
|
+
# default value.
|
60
|
+
#
|
61
|
+
# @param [Symbol] field the field to get the value for
|
62
|
+
# @param [Object] default the value to return if the field has not been set
|
63
|
+
# @return [Object] the value of the given field else the given default value
|
64
|
+
def fetch(field, default)
|
65
|
+
@attribute_hash.fetch(field.to_sym, default)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calls the block once for each attribute, passing the key/value pair as parameters.
|
69
|
+
# If no block is given, an enumerator is returned instead.
|
70
|
+
#
|
71
|
+
# @yieldparam [Symbol] field the struct field for the current iteration
|
72
|
+
# @yieldparam [Object] value the value of the current field
|
73
|
+
#
|
74
|
+
# @return [Enumerable] when no block is given
|
75
|
+
def each_pair
|
76
|
+
return enum_for(:each_pair) unless block_given?
|
77
|
+
@attribute_hash.each do |field, value|
|
78
|
+
yield(field, value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Converts the `ValueStruct` to a `Hash` with keys representing each attribute
|
83
|
+
# (as symbols) and their corresponding values.
|
84
|
+
#
|
85
|
+
# @return [Hash] a `Hash` representing this struct
|
86
|
+
def to_h
|
87
|
+
@attribute_hash.dup # dup removes the frozen flag
|
88
|
+
end
|
89
|
+
|
90
|
+
# Compares this object and other for equality. A `ValueStruct` is `eql?` to
|
91
|
+
# other when other is a `ValueStruct` and the two objects have identical
|
92
|
+
# fields and values.
|
93
|
+
#
|
94
|
+
# @param [Object] other the other record to compare for equality
|
95
|
+
# @return [Boolean] true when equal else false
|
96
|
+
def eql?(other)
|
97
|
+
other.is_a?(self.class) && @attribute_hash == other.to_h
|
98
|
+
end
|
99
|
+
alias_method :==, :eql?
|
100
|
+
|
101
|
+
# Describe the contents of this object in a string.
|
102
|
+
#
|
103
|
+
# @return [String] the string representation of this object
|
104
|
+
#
|
105
|
+
# @!visibility private
|
106
|
+
def inspect
|
107
|
+
state = @attribute_hash.to_s.gsub(/^{/, '').gsub(/}$/, '')
|
108
|
+
"#<#{self.class} #{state}>"
|
109
|
+
end
|
110
|
+
alias_method :to_s, :inspect
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
# Set the value of the give field to the given value.
|
115
|
+
#
|
116
|
+
# @param [Symbol] field the field to set the value for
|
117
|
+
# @param [Object] value the value to set the field to
|
118
|
+
# @return [Object] the final value of the given field
|
119
|
+
#
|
120
|
+
# @!visibility private
|
121
|
+
def set_attribute(field, value)
|
122
|
+
@attribute_hash[field.to_sym] = value
|
123
|
+
end
|
124
|
+
|
125
|
+
# Check the method name and args for signatures matching potential
|
126
|
+
# final predicate methods. If the signature matches call the appropriate
|
127
|
+
# method
|
128
|
+
#
|
129
|
+
# @param [Symbol] symbol the name of the called function
|
130
|
+
# @param [Array] args zero or more arguments
|
131
|
+
# @return [Object] the result of the proxied method or the `super` call
|
132
|
+
#
|
133
|
+
# @!visibility private
|
134
|
+
def method_missing(symbol, *args)
|
135
|
+
if args.length == 0 && (match = /([^\?]+)\?$/.match(symbol))
|
136
|
+
set?(match[1])
|
137
|
+
elsif args.length == 0 && set?(symbol)
|
138
|
+
get(symbol)
|
139
|
+
else
|
140
|
+
super
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/functional/version.rb
CHANGED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Functional
|
5
|
+
|
6
|
+
describe FinalStruct do
|
7
|
+
|
8
|
+
context 'instanciation' do
|
9
|
+
|
10
|
+
specify 'with no args defines no fields' do
|
11
|
+
subject = FinalStruct.new
|
12
|
+
expect(subject.to_h).to be_empty
|
13
|
+
end
|
14
|
+
|
15
|
+
specify 'with a hash sets fields using has values' do
|
16
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
17
|
+
expect(subject.foo).to eq 1
|
18
|
+
expect(subject.bar).to eq :two
|
19
|
+
expect(subject.baz).to eq 'three'
|
20
|
+
end
|
21
|
+
|
22
|
+
specify 'with a hash creates true predicates for has keys' do
|
23
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
24
|
+
expect(subject.foo?).to be true
|
25
|
+
expect(subject.bar?).to be true
|
26
|
+
expect(subject.baz?).to be true
|
27
|
+
end
|
28
|
+
|
29
|
+
specify 'can be created from any object that responds to #to_h' do
|
30
|
+
clazz = Class.new do
|
31
|
+
def to_h; {answer: 42, harmless: 'mostly'}; end
|
32
|
+
end
|
33
|
+
struct = clazz.new
|
34
|
+
subject = FinalStruct.new(struct)
|
35
|
+
expect(subject.answer).to eq 42
|
36
|
+
expect(subject.harmless).to eq 'mostly'
|
37
|
+
end
|
38
|
+
|
39
|
+
specify 'raises an exception if given a non-hash argument' do
|
40
|
+
expect {
|
41
|
+
FinalStruct.new(:bogus)
|
42
|
+
}.to raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'set fields' do
|
47
|
+
|
48
|
+
subject do
|
49
|
+
struct = FinalStruct.new
|
50
|
+
struct.foo = 42
|
51
|
+
struct.bar = "Don't Panic"
|
52
|
+
struct
|
53
|
+
end
|
54
|
+
|
55
|
+
specify 'have a reader which returns the value' do
|
56
|
+
expect(subject.foo).to eq 42
|
57
|
+
expect(subject.bar).to eq "Don't Panic"
|
58
|
+
end
|
59
|
+
|
60
|
+
specify 'have a predicate which returns true' do
|
61
|
+
expect(subject.foo?).to be true
|
62
|
+
expect(subject.bar?).to be true
|
63
|
+
end
|
64
|
+
|
65
|
+
specify 'raise an exception when written to again' do
|
66
|
+
expect {subject.foo = 0}.to raise_error(Functional::FinalityError)
|
67
|
+
expect {subject.bar = 0}.to raise_error(Functional::FinalityError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'unset fields' do
|
72
|
+
|
73
|
+
subject { FinalStruct.new }
|
74
|
+
|
75
|
+
specify 'have a magic reader that always returns nil' do
|
76
|
+
expect(subject.foo).to be nil
|
77
|
+
expect(subject.bar).to be nil
|
78
|
+
expect(subject.baz).to be nil
|
79
|
+
end
|
80
|
+
|
81
|
+
specify 'have a magic predicate that always returns false' do
|
82
|
+
expect(subject.foo?).to be false
|
83
|
+
expect(subject.bar?).to be false
|
84
|
+
expect(subject.baz?).to be false
|
85
|
+
end
|
86
|
+
|
87
|
+
specify 'have a magic writer that sets the field' do
|
88
|
+
expect(subject.foo = 42).to eq 42
|
89
|
+
expect(subject.bar = :towel).to eq :towel
|
90
|
+
expect(subject.baz = "Don't Panic").to eq "Don't Panic"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'accessors' do
|
95
|
+
|
96
|
+
let!(:field_value_pairs) { {foo: 1, bar: :two, baz: 'three'} }
|
97
|
+
|
98
|
+
subject { FinalStruct.new(field_value_pairs) }
|
99
|
+
|
100
|
+
specify '#get returns the value of a set field' do
|
101
|
+
expect(subject.get(:foo)).to eq 1
|
102
|
+
end
|
103
|
+
|
104
|
+
specify '#get returns nil for an unset field' do
|
105
|
+
expect(subject.get(:bogus)).to be nil
|
106
|
+
end
|
107
|
+
|
108
|
+
specify '#[] is an alias for #get' do
|
109
|
+
expect(subject[:foo]).to eq 1
|
110
|
+
expect(subject[:bogus]).to be nil
|
111
|
+
end
|
112
|
+
|
113
|
+
specify '#set sets the value of an unset field' do
|
114
|
+
subject.set(:harmless, 'mostly')
|
115
|
+
expect(subject.harmless).to eq 'mostly'
|
116
|
+
expect(subject.harmless?).to be true
|
117
|
+
end
|
118
|
+
|
119
|
+
specify '#set raises an exception if the field has already been set' do
|
120
|
+
subject.set(:harmless, 'mostly')
|
121
|
+
expect {
|
122
|
+
subject.set(:harmless, 'extremely')
|
123
|
+
}.to raise_error(Functional::FinalityError)
|
124
|
+
end
|
125
|
+
|
126
|
+
specify '#[]= is an alias for set' do
|
127
|
+
subject[:harmless] = 'mostly'
|
128
|
+
expect(subject.harmless).to eq 'mostly'
|
129
|
+
expect {
|
130
|
+
subject[:harmless] = 'extremely'
|
131
|
+
}.to raise_error(Functional::FinalityError)
|
132
|
+
end
|
133
|
+
|
134
|
+
specify '#set? returns false for an unset field' do
|
135
|
+
expect(subject.set?(:harmless)).to be false
|
136
|
+
end
|
137
|
+
|
138
|
+
specify '#set? returns true for a field that has been set' do
|
139
|
+
subject.set(:harmless, 'mostly')
|
140
|
+
expect(subject.set?(:harmless)).to be true
|
141
|
+
end
|
142
|
+
|
143
|
+
specify '#get_or_set returns the value of a set field' do
|
144
|
+
subject.answer = 42
|
145
|
+
expect(subject.get_or_set(:answer, 100)).to eq 42
|
146
|
+
end
|
147
|
+
|
148
|
+
specify '#get_or_set sets the value of an unset field' do
|
149
|
+
subject.get_or_set(:answer, 42)
|
150
|
+
expect(subject.answer).to eq 42
|
151
|
+
expect(subject.answer?).to be true
|
152
|
+
end
|
153
|
+
|
154
|
+
specify '#get_or_set returns the value of a newly set field' do
|
155
|
+
expect(subject.get_or_set(:answer, 42)).to eq 42
|
156
|
+
end
|
157
|
+
|
158
|
+
specify '#fetch gets the value of a set field' do
|
159
|
+
subject.harmless = 'mostly'
|
160
|
+
expect(subject.fetch(:harmless, 'extremely')).to eq 'mostly'
|
161
|
+
end
|
162
|
+
|
163
|
+
specify '#fetch returns the given value when the field is unset' do
|
164
|
+
expect(subject.fetch(:harmless, 'extremely')).to eq 'extremely'
|
165
|
+
end
|
166
|
+
|
167
|
+
specify '#fetch does not set an unset field' do
|
168
|
+
subject.fetch(:answer, 42)
|
169
|
+
expect(subject.answer).to be_nil
|
170
|
+
expect(subject.answer?).to be false
|
171
|
+
end
|
172
|
+
|
173
|
+
specify '#to_h returns the key/value pairs for all set values' do
|
174
|
+
subject = FinalStruct.new(field_value_pairs)
|
175
|
+
expect(subject.to_h).to eq field_value_pairs
|
176
|
+
end
|
177
|
+
|
178
|
+
specify '#to_h is updated when new fields are added' do
|
179
|
+
subject = FinalStruct.new
|
180
|
+
field_value_pairs.each_pair do |field, value|
|
181
|
+
subject.set(field, value)
|
182
|
+
end
|
183
|
+
expect(subject.to_h).to eq field_value_pairs
|
184
|
+
end
|
185
|
+
|
186
|
+
specify '#each_pair returns an Enumerable when no block given' do
|
187
|
+
subject = FinalStruct.new(field_value_pairs)
|
188
|
+
expect(subject.each_pair).to be_a Enumerable
|
189
|
+
end
|
190
|
+
|
191
|
+
specify '#each_pair enumerates over each field/value pair' do
|
192
|
+
subject = FinalStruct.new(field_value_pairs)
|
193
|
+
result = {}
|
194
|
+
|
195
|
+
subject.each_pair do |field, value|
|
196
|
+
result[field] = value
|
197
|
+
end
|
198
|
+
|
199
|
+
expect(result).to eq field_value_pairs
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'reflection' do
|
204
|
+
|
205
|
+
specify '#eql? returns true when both define the same fields with the same values' do
|
206
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
207
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
208
|
+
|
209
|
+
expect(first.eql?(second)).to be true
|
210
|
+
expect(first == second).to be true
|
211
|
+
end
|
212
|
+
|
213
|
+
specify '#eql? returns false when other has different fields defined' do
|
214
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
215
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two)
|
216
|
+
|
217
|
+
expect(first.eql?(second)).to be false
|
218
|
+
expect(first == second).to be false
|
219
|
+
end
|
220
|
+
|
221
|
+
specify '#eql? returns false when other has different field values' do
|
222
|
+
first = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
223
|
+
second = FinalStruct.new(foo: 1, 'bar' => :two, baz: 3)
|
224
|
+
|
225
|
+
expect(first.eql?(second)).to be false
|
226
|
+
expect(first == second).to be false
|
227
|
+
end
|
228
|
+
|
229
|
+
specify '#eql? returns false when other is not a FinalStruct' do
|
230
|
+
attributes = {answer: 42, harmless: 'mostly'}
|
231
|
+
clazz = Class.new do
|
232
|
+
def to_h; {answer: 42, harmless: 'mostly'}; end
|
233
|
+
end
|
234
|
+
other = clazz.new
|
235
|
+
subject = FinalStruct.new(attributes)
|
236
|
+
expect(subject.eql?(other)).to be false
|
237
|
+
expect(subject == other).to be false
|
238
|
+
end
|
239
|
+
|
240
|
+
specify '#inspect begins with the class name' do
|
241
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
242
|
+
expect(subject.inspect).to match(/^#<#{described_class}\s+/)
|
243
|
+
end
|
244
|
+
|
245
|
+
specify '#inspect includes all field/value pairs' do
|
246
|
+
field_value_pairs = {foo: 1, 'bar' => :two, baz: 'three'}
|
247
|
+
subject = FinalStruct.new(field_value_pairs)
|
248
|
+
|
249
|
+
field_value_pairs.each do |field, value|
|
250
|
+
expect(subject.inspect).to match(/:#{field}=>"?:?#{value}"?/)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
specify '#to_s returns the same value as #inspect' do
|
255
|
+
subject = FinalStruct.new(foo: 1, 'bar' => :two, baz: 'three')
|
256
|
+
expect(subject.to_s).to eq subject.inspect
|
257
|
+
end
|
258
|
+
|
259
|
+
specify '#method_missing raises an exception for methods with unrecognized signatures' do
|
260
|
+
expect {
|
261
|
+
subject.foo(1, 2, 3)
|
262
|
+
}.to raise_error(NoMethodError)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
describe FinalVar do
|
6
|
+
|
7
|
+
context 'instanciation' do
|
8
|
+
|
9
|
+
it 'is unset when no arguments given' do
|
10
|
+
expect(FinalVar.new).to_not be_set
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is set with the given argument' do
|
14
|
+
expect(FinalVar.new(41)).to be_set
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#get' do
|
19
|
+
|
20
|
+
subject { FinalVar.new }
|
21
|
+
|
22
|
+
it 'returns nil when unset' do
|
23
|
+
expect(subject.get).to be nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns the value when set' do
|
27
|
+
expect(FinalVar.new(42).get).to eq 42
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is aliased as #value' do
|
31
|
+
expect(subject.value).to be nil
|
32
|
+
subject.set(42)
|
33
|
+
expect(subject.value).to eq 42
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context '#set' do
|
38
|
+
|
39
|
+
subject { FinalVar.new }
|
40
|
+
|
41
|
+
it 'sets the value when unset' do
|
42
|
+
subject.set(42)
|
43
|
+
expect(subject.get).to eq 42
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns the new value when unset' do
|
47
|
+
expect(subject.set(42)).to eq 42
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an exception when already set' do
|
51
|
+
subject.set(42)
|
52
|
+
expect {
|
53
|
+
subject.set(42)
|
54
|
+
}.to raise_error(Functional::FinalityError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'is aliased as #value=' do
|
58
|
+
subject.value = 42
|
59
|
+
expect(subject.get).to eq 42
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#set?' do
|
64
|
+
|
65
|
+
it 'returns false when unset' do
|
66
|
+
expect(FinalVar.new).to_not be_set
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns true when set' do
|
70
|
+
expect(FinalVar.new(42)).to be_set
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'is aliased as value?' do
|
74
|
+
expect(FinalVar.new.value?).to be false
|
75
|
+
expect(FinalVar.new(42).value?).to be true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context '#get_or_set' do
|
80
|
+
|
81
|
+
it 'sets the value when unset' do
|
82
|
+
subject = FinalVar.new
|
83
|
+
subject.get_or_set(42)
|
84
|
+
expect(subject.get).to eq 42
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns the new value when previously unset' do
|
88
|
+
subject = FinalVar.new
|
89
|
+
expect(subject.get_or_set(42)).to eq 42
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns the current value when already set' do
|
93
|
+
subject = FinalVar.new(100)
|
94
|
+
expect(subject.get_or_set(42)).to eq 100
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context '#fetch' do
|
99
|
+
|
100
|
+
it 'returns the given default value when unset' do
|
101
|
+
subject = FinalVar.new
|
102
|
+
expect(subject.fetch(42)).to eq 42
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'does not change the current value when unset' do
|
106
|
+
subject = FinalVar.new
|
107
|
+
subject.fetch(42)
|
108
|
+
expect(subject.get).to be nil
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns the current value when already set' do
|
112
|
+
subject = FinalVar.new(100)
|
113
|
+
expect(subject.get_or_set(42)).to eq 100
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'reflection' do
|
118
|
+
|
119
|
+
specify '#eql? returns false when unset' do
|
120
|
+
expect(FinalVar.new.eql?(nil)).to be false
|
121
|
+
expect(FinalVar.new.eql?(42)).to be false
|
122
|
+
expect(FinalVar.new.eql?(FinalVar.new.value)).to be false
|
123
|
+
end
|
124
|
+
|
125
|
+
specify '#eql? returns false when set and the value does not match other' do
|
126
|
+
subject = FinalVar.new(42)
|
127
|
+
expect(subject.eql?(100)).to be false
|
128
|
+
end
|
129
|
+
|
130
|
+
specify '#eql? returns true when set and the value matches other' do
|
131
|
+
subject = FinalVar.new(42)
|
132
|
+
expect(subject.eql?(42)).to be true
|
133
|
+
end
|
134
|
+
|
135
|
+
specify '#eql? returns true when set and other is a FinalVar with the same value' do
|
136
|
+
subject = FinalVar.new(42)
|
137
|
+
other = FinalVar.new(42)
|
138
|
+
expect(subject.eql?(other)).to be true
|
139
|
+
end
|
140
|
+
|
141
|
+
specify 'aliases #== as #eql?' do
|
142
|
+
expect(FinalVar.new == nil).to be false
|
143
|
+
expect(FinalVar.new == 42).to be false
|
144
|
+
expect(FinalVar.new == FinalVar.new).to be false
|
145
|
+
expect(FinalVar.new(42) == 42).to be true
|
146
|
+
expect(FinalVar.new(42) == FinalVar.new(42)).to be true
|
147
|
+
end
|
148
|
+
|
149
|
+
specify '#inspect includes the word "value" and the value when set' do
|
150
|
+
subject = FinalVar.new(42)
|
151
|
+
expect(subject.inspect).to match(/value\s?=\s?42\s*>$/)
|
152
|
+
end
|
153
|
+
|
154
|
+
specify '#inspect include the word "unset" when unset' do
|
155
|
+
subject = FinalVar.new
|
156
|
+
expect(subject.inspect).to match(/unset\s*>$/i)
|
157
|
+
end
|
158
|
+
|
159
|
+
specify '#to_s returns nil as a string when unset' do
|
160
|
+
expect(FinalVar.new.to_s).to eq nil.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
specify '#to_s returns the value as a string when set' do
|
164
|
+
expect(FinalVar.new(42).to_s).to eq 42.to_s
|
165
|
+
expect(FinalVar.new('42').to_s).to eq '42'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|