attributor 5.0.2 → 5.1.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/.rubocop.yml +30 -0
- data/.travis.yml +6 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile +1 -1
- data/Guardfile +14 -8
- data/Rakefile +4 -5
- data/attributor.gemspec +34 -29
- data/lib/attributor.rb +23 -29
- data/lib/attributor/attribute.rb +108 -127
- data/lib/attributor/attribute_resolver.rb +12 -26
- data/lib/attributor/dsl_compiler.rb +17 -21
- data/lib/attributor/dumpable.rb +1 -2
- data/lib/attributor/example_mixin.rb +5 -8
- data/lib/attributor/exceptions.rb +5 -6
- data/lib/attributor/extensions/randexp.rb +3 -5
- data/lib/attributor/extras/field_selector.rb +4 -4
- data/lib/attributor/extras/field_selector/transformer.rb +6 -7
- data/lib/attributor/families/numeric.rb +0 -2
- data/lib/attributor/families/temporal.rb +1 -4
- data/lib/attributor/hash_dsl_compiler.rb +22 -25
- data/lib/attributor/type.rb +24 -32
- data/lib/attributor/types/bigdecimal.rb +7 -14
- data/lib/attributor/types/boolean.rb +5 -8
- data/lib/attributor/types/class.rb +9 -10
- data/lib/attributor/types/collection.rb +34 -44
- data/lib/attributor/types/container.rb +9 -15
- data/lib/attributor/types/csv.rb +7 -10
- data/lib/attributor/types/date.rb +20 -25
- data/lib/attributor/types/date_time.rb +7 -14
- data/lib/attributor/types/float.rb +4 -6
- data/lib/attributor/types/hash.rb +171 -196
- data/lib/attributor/types/ids.rb +2 -6
- data/lib/attributor/types/integer.rb +12 -17
- data/lib/attributor/types/model.rb +39 -48
- data/lib/attributor/types/object.rb +2 -4
- data/lib/attributor/types/polymorphic.rb +118 -0
- data/lib/attributor/types/regexp.rb +4 -5
- data/lib/attributor/types/string.rb +6 -7
- data/lib/attributor/types/struct.rb +8 -15
- data/lib/attributor/types/symbol.rb +3 -6
- data/lib/attributor/types/tempfile.rb +5 -6
- data/lib/attributor/types/time.rb +11 -11
- data/lib/attributor/types/uri.rb +9 -10
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_resolver_spec.rb +57 -78
- data/spec/attribute_spec.rb +174 -216
- data/spec/attributor_spec.rb +11 -15
- data/spec/dsl_compiler_spec.rb +19 -33
- data/spec/dumpable_spec.rb +6 -7
- data/spec/extras/field_selector/field_selector_spec.rb +1 -1
- data/spec/families_spec.rb +1 -3
- data/spec/hash_dsl_compiler_spec.rb +65 -74
- data/spec/spec_helper.rb +9 -3
- data/spec/support/hashes.rb +2 -3
- data/spec/support/models.rb +30 -36
- data/spec/support/polymorphics.rb +10 -0
- data/spec/type_spec.rb +38 -61
- data/spec/types/bigdecimal_spec.rb +11 -15
- data/spec/types/boolean_spec.rb +12 -39
- data/spec/types/class_spec.rb +10 -11
- data/spec/types/collection_spec.rb +72 -81
- data/spec/types/container_spec.rb +22 -26
- data/spec/types/csv_spec.rb +15 -16
- data/spec/types/date_spec.rb +16 -33
- data/spec/types/date_time_spec.rb +16 -33
- data/spec/types/file_upload_spec.rb +1 -2
- data/spec/types/float_spec.rb +7 -14
- data/spec/types/hash_spec.rb +285 -289
- data/spec/types/ids_spec.rb +5 -7
- data/spec/types/integer_spec.rb +37 -46
- data/spec/types/model_spec.rb +111 -128
- data/spec/types/polymorphic_spec.rb +134 -0
- data/spec/types/regexp_spec.rb +4 -7
- data/spec/types/string_spec.rb +17 -21
- data/spec/types/struct_spec.rb +40 -47
- data/spec/types/tempfile_spec.rb +1 -2
- data/spec/types/temporal_spec.rb +9 -0
- data/spec/types/time_spec.rb +16 -32
- data/spec/types/type_spec.rb +15 -0
- data/spec/types/uri_spec.rb +6 -7
- metadata +77 -25
data/spec/types/hash_spec.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
2
|
|
3
|
-
|
4
3
|
describe Attributor::Hash do
|
5
|
-
|
6
4
|
subject(:type) { Attributor::Hash }
|
7
5
|
|
8
6
|
its(:native_type) { should be(type) }
|
@@ -16,45 +14,63 @@ describe Attributor::Hash do
|
|
16
14
|
Class.new(Attributor::Model) do
|
17
15
|
attributes do
|
18
16
|
raise 'sorry :('
|
19
|
-
attribute :name, String
|
20
17
|
end
|
21
18
|
end
|
22
19
|
end
|
23
20
|
|
24
21
|
it 'throws original exception upon first run' do
|
25
|
-
|
22
|
+
expect do
|
26
23
|
broken_model.attributes
|
27
|
-
|
24
|
+
end.to raise_error(RuntimeError, 'sorry :(')
|
28
25
|
end
|
26
|
+
context 'subsequent use' do
|
27
|
+
before do
|
28
|
+
expect do
|
29
|
+
broken_model.attributes
|
30
|
+
end.to raise_error(RuntimeError, 'sorry :(')
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
}.should raise_error(Attributor::InvalidDefinition)
|
36
|
-
end
|
33
|
+
it 'throws InvalidDefinition for subsequent access' do
|
34
|
+
expect do
|
35
|
+
broken_model.attributes
|
36
|
+
end.to raise_error(Attributor::InvalidDefinition)
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
39
|
+
it 'throws for any attempts at using of an instance of it' do
|
40
|
+
instance = broken_model.new
|
41
|
+
expect do
|
42
|
+
instance.name
|
43
|
+
end.to raise_error(Attributor::InvalidDefinition)
|
44
|
+
end
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
context 'for a type with a name' do
|
47
|
+
subject(:broken_model) do
|
48
|
+
Class.new(Attributor::Model) do
|
49
|
+
def self.name
|
50
|
+
'BrokenModel'
|
51
|
+
end
|
52
|
+
attributes do
|
53
|
+
raise 'sorry :('
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
it 'includes the correct type.name if applicable' do
|
58
|
+
expect do
|
59
|
+
broken_model.attributes
|
60
|
+
end.to raise_error(Attributor::InvalidDefinition, /BrokenModel/)
|
61
|
+
end
|
62
|
+
end
|
45
63
|
end
|
46
|
-
|
47
64
|
end
|
48
65
|
end
|
49
66
|
|
50
67
|
context 'default options' do
|
51
68
|
subject(:options) { type.options }
|
52
69
|
it 'has allow_extra false' do
|
53
|
-
options[:allow_extra].
|
70
|
+
expect(options[:allow_extra]).to be(false)
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|
57
|
-
|
58
74
|
context '.example' do
|
59
75
|
context 'for a simple hash' do
|
60
76
|
subject(:example) { Attributor::Hash.example }
|
@@ -65,12 +81,12 @@ describe Attributor::Hash do
|
|
65
81
|
end
|
66
82
|
|
67
83
|
context 'for a typed hash' do
|
68
|
-
subject(:example){ Attributor::Hash.of(value: Integer).example}
|
84
|
+
subject(:example) { Attributor::Hash.of(value: Integer).example }
|
69
85
|
|
70
86
|
it 'returns a hash with keys and/or values of the right type' do
|
71
|
-
example.
|
72
|
-
example.keys.size.
|
73
|
-
example.values.all? {|v| v.
|
87
|
+
expect(example).to be_kind_of(Attributor::Hash)
|
88
|
+
expect(example.keys.size).to be > 0
|
89
|
+
expect(example.values.all? { |v| v.is_a? Integer }).to be(true)
|
74
90
|
end
|
75
91
|
end
|
76
92
|
|
@@ -81,128 +97,129 @@ describe Attributor::Hash do
|
|
81
97
|
subject(:example) { HashWithStrings.example(name: name, something: something) }
|
82
98
|
|
83
99
|
context 'resolves a lazy attributes on demand' do
|
84
|
-
before { example.lazy_attributes.keys.
|
85
|
-
after { example.lazy_attributes.keys.
|
100
|
+
before { expect(example.lazy_attributes.keys).to eq [:name, :something] }
|
101
|
+
after { expect(example.lazy_attributes.keys).to eq [:something] }
|
86
102
|
|
87
103
|
it 'using get' do
|
88
|
-
example.get(:name).
|
104
|
+
expect(example.get(:name)).to be name
|
89
105
|
end
|
90
106
|
it 'using []' do
|
91
|
-
example[:name].
|
107
|
+
expect(example[:name]).to be name
|
92
108
|
end
|
93
109
|
|
94
110
|
it 'using set' do
|
95
111
|
example.set :name, 'not bob'
|
96
|
-
example.get(:name).
|
112
|
+
expect(example.get(:name)).to eq 'not bob'
|
97
113
|
end
|
98
114
|
it 'using []=' do
|
99
115
|
example[:name] = 'not bob'
|
100
|
-
example[:name].
|
116
|
+
expect(example[:name]).to eq 'not bob'
|
101
117
|
end
|
102
118
|
end
|
103
119
|
|
104
120
|
its(:size) { should eq 2 }
|
105
|
-
its(:values) { should
|
106
|
-
its(:keys) { should
|
121
|
+
its(:values) { should match_array [name, something] }
|
122
|
+
its(:keys) { should match_array [:name, :something] }
|
107
123
|
it do
|
108
124
|
should_not be_empty
|
109
125
|
end
|
110
126
|
|
111
127
|
it 'responds to key? correctly' do
|
112
|
-
example.key?(:name).
|
113
|
-
example.key?(:something).
|
128
|
+
expect(example.key?(:name)).to be(true)
|
129
|
+
expect(example.key?(:something)).to be(true)
|
114
130
|
end
|
115
131
|
|
116
132
|
it 'enumerates the contents' do
|
117
|
-
example.collect {|k,
|
133
|
+
expect(example.collect { |k, _v| k }).to eq [:name, :something]
|
118
134
|
end
|
119
135
|
|
120
136
|
it 'enumerates the contents using each_pair' do
|
121
137
|
pairs = []
|
122
|
-
example.each_pair {|pair| pairs << pair }
|
123
|
-
pairs.
|
138
|
+
example.each_pair { |pair| pairs << pair }
|
139
|
+
expect(pairs).to match_array [[:name, name], [:something, something]]
|
124
140
|
end
|
125
141
|
|
126
|
-
its(:contents){ should eq
|
142
|
+
its(:contents) { should eq(name: name, something: something) }
|
127
143
|
it 'does not create methods for the keys' do
|
128
|
-
example.
|
129
|
-
example.
|
144
|
+
expect(example).not_to respond_to(:name)
|
145
|
+
expect(example).not_to respond_to(:something)
|
130
146
|
end
|
131
|
-
|
132
147
|
end
|
133
148
|
|
134
149
|
context 'using a non array context' do
|
135
150
|
it 'should work for hashes with key/value types' do
|
136
|
-
expect
|
151
|
+
expect do
|
152
|
+
Attributor::Hash.of(key: String, value: String)
|
153
|
+
.example('Not an Array')
|
154
|
+
end.to_not raise_error
|
137
155
|
end
|
138
156
|
it 'should work for hashes with keys defined' do
|
139
157
|
block = proc { key 'a string', String }
|
140
|
-
hash = Attributor::Hash.of(key:String).construct(block)
|
158
|
+
hash = Attributor::Hash.of(key: String).construct(block)
|
141
159
|
|
142
|
-
expect{ hash.example(
|
160
|
+
expect { hash.example('Not an Array') }.to_not raise_error
|
143
161
|
end
|
144
162
|
end
|
145
163
|
end
|
146
164
|
|
147
165
|
context '.load' do
|
148
|
-
let(:value) { {one: 'two', three: 4} }
|
166
|
+
let(:value) { { one: 'two', three: 4 } }
|
149
167
|
subject(:hash) { type.load(value) }
|
150
168
|
|
151
169
|
context 'for nil with recurse: true' do
|
152
170
|
let(:value) { nil }
|
153
|
-
subject(:hash) { HashWithModel.load(value, recurse:true) }
|
171
|
+
subject(:hash) { HashWithModel.load(value, recurse: true) }
|
154
172
|
|
155
173
|
it 'works' do
|
156
|
-
hash[:name].
|
157
|
-
hash[:chicken].age.
|
174
|
+
expect(hash[:name]).to eq('Turkey McDucken')
|
175
|
+
expect(hash[:chicken].age).to eq(1)
|
158
176
|
end
|
159
177
|
end
|
160
178
|
|
161
179
|
context 'for a simple hash' do
|
162
180
|
it { should eq(value) }
|
163
181
|
it 'equals the hash' do
|
164
|
-
hash.
|
165
|
-
hash[:one].
|
166
|
-
hash[:three].
|
182
|
+
expect(hash).to eq value
|
183
|
+
expect(hash[:one]).to eq('two')
|
184
|
+
expect(hash[:three]).to eq(4)
|
167
185
|
end
|
168
186
|
end
|
169
187
|
|
170
188
|
context 'for a JSON encoded hash' do
|
171
|
-
let(:value_as_hash) { {'one' => 'two', 'three' => 4} }
|
172
|
-
let(:value) { JSON.dump(
|
189
|
+
let(:value_as_hash) { { 'one' => 'two', 'three' => 4 } }
|
190
|
+
let(:value) { JSON.dump(value_as_hash) }
|
173
191
|
it 'deserializes and converts it to a real hash' do
|
174
|
-
hash.
|
175
|
-
hash['one'].
|
192
|
+
expect(hash).to eq(value_as_hash)
|
193
|
+
expect(hash['one']).to eq 'two'
|
176
194
|
end
|
177
195
|
end
|
178
196
|
|
179
197
|
context 'for a typed hash' do
|
180
|
-
subject(:type){ Attributor::Hash.of(key: String, value: Integer)}
|
198
|
+
subject(:type) { Attributor::Hash.of(key: String, value: Integer) }
|
181
199
|
context 'with good values' do
|
182
|
-
let(:value) { {one: '1', 'three' => 3} }
|
200
|
+
let(:value) { { one: '1', 'three' => 3 } }
|
183
201
|
it 'coerces good values into the correct types' do
|
184
|
-
hash.
|
185
|
-
hash['one'].
|
202
|
+
expect(hash).to eq('one' => 1, 'three' => 3)
|
203
|
+
expect(hash['one']).to eq(1)
|
186
204
|
end
|
187
205
|
end
|
188
206
|
|
189
207
|
context 'with incompatible values' do
|
190
|
-
let(:value) { {one: 'two', three: 4} }
|
208
|
+
let(:value) { { one: 'two', three: 4 } }
|
191
209
|
it 'fails' do
|
192
|
-
expect
|
210
|
+
expect do
|
193
211
|
type.load(value)
|
194
|
-
|
212
|
+
end.to raise_error(/invalid value for Integer/)
|
195
213
|
end
|
196
214
|
end
|
197
|
-
|
198
215
|
end
|
199
216
|
|
200
217
|
context 'for a partially typed hash' do
|
201
|
-
subject(:type){ Attributor::Hash.of(value: Integer) }
|
218
|
+
subject(:type) { Attributor::Hash.of(value: Integer) }
|
202
219
|
context 'with good values' do
|
203
|
-
let(:value) { {one: '1', [1,2,3] => 3} }
|
220
|
+
let(:value) { { one: '1', [1, 2, 3] => 3 } }
|
204
221
|
it 'coerces only values into the correct types (and leave keys alone)' do
|
205
|
-
hash.
|
222
|
+
expect(hash).to eq(:one => 1, [1, 2, 3] => 3)
|
206
223
|
end
|
207
224
|
end
|
208
225
|
end
|
@@ -222,20 +239,20 @@ describe Attributor::Hash do
|
|
222
239
|
Class.new(Attributor::Hash) do
|
223
240
|
keys do
|
224
241
|
key 'id', Integer
|
225
|
-
key 'name', String, default:
|
242
|
+
key 'name', String, default: 'unnamed'
|
226
243
|
key 'chicken', Chicken
|
227
244
|
end
|
228
245
|
end
|
229
246
|
end
|
230
247
|
|
231
|
-
let(:value) { {'chicken' => Chicken.example} }
|
248
|
+
let(:value) { { 'chicken' => Chicken.example } }
|
232
249
|
|
233
250
|
subject(:hash) { type.load(value) }
|
234
251
|
|
235
252
|
it { should_not have_key('id') }
|
236
253
|
it 'has the defaulted key' do
|
237
|
-
hash.
|
238
|
-
hash['name'].
|
254
|
+
expect(hash).to have_key('name')
|
255
|
+
expect(hash['name']).to eq('unnamed')
|
239
256
|
end
|
240
257
|
end
|
241
258
|
|
@@ -273,44 +290,39 @@ describe Attributor::Hash do
|
|
273
290
|
end
|
274
291
|
|
275
292
|
it 'complains about an unknown key' do
|
276
|
-
expect
|
293
|
+
expect do
|
277
294
|
loader_hash.load(value)
|
278
|
-
|
295
|
+
end.to raise_error(Attributor::AttributorException,
|
296
|
+
/Unknown key received: :weird_key/)
|
279
297
|
end
|
280
298
|
end
|
281
299
|
end
|
282
300
|
end
|
283
|
-
|
284
|
-
|
285
301
|
end
|
286
302
|
|
287
|
-
|
288
303
|
context '.of' do
|
289
304
|
context 'specific key and value types' do
|
290
|
-
let(:key_type){ String }
|
291
|
-
let(:value_type){ Integer }
|
305
|
+
let(:key_type) { String }
|
306
|
+
let(:value_type) { Integer }
|
292
307
|
|
293
308
|
subject(:type) { Attributor::Hash.of(key: key_type, value: value_type) }
|
294
309
|
|
295
310
|
it { should be_a(::Class) }
|
296
311
|
its(:ancestors) { should include(Attributor::Hash) }
|
297
|
-
its(:key_type) { should
|
298
|
-
its(:value_type) { should
|
312
|
+
its(:key_type) { should eq Attributor::String }
|
313
|
+
its(:value_type) { should eq Attributor::Integer }
|
299
314
|
|
300
315
|
context '.load' do
|
301
|
-
let(:value) { {one: '2', 3 => 4} }
|
316
|
+
let(:value) { { one: '2', 3 => 4 } }
|
302
317
|
|
303
318
|
subject(:hash) { type.load(value) }
|
304
319
|
|
305
320
|
it 'coerces the types properly' do
|
306
|
-
hash['one'].
|
307
|
-
hash['3'].
|
321
|
+
expect(hash['one']).to eq(2)
|
322
|
+
expect(hash['3']).to eq(4)
|
308
323
|
end
|
309
|
-
|
310
324
|
end
|
311
|
-
|
312
325
|
end
|
313
|
-
|
314
326
|
end
|
315
327
|
|
316
328
|
context '.construct' do
|
@@ -323,36 +335,35 @@ describe Attributor::Hash do
|
|
323
335
|
end
|
324
336
|
end
|
325
337
|
|
326
|
-
|
327
338
|
subject(:type) { Attributor::Hash.construct(block) }
|
328
339
|
|
329
|
-
it
|
330
|
-
|
340
|
+
it do
|
341
|
+
should_not be(Attributor::Hash)
|
342
|
+
end
|
331
343
|
|
332
344
|
context 'loading' do
|
333
|
-
let(:date) { DateTime.parse(
|
345
|
+
let(:date) { DateTime.parse('2014-07-15') }
|
334
346
|
let(:value) do
|
335
|
-
{'a string' => 12, '1' => '2', :some_date => date.to_s}
|
347
|
+
{ 'a string' => 12, '1' => '2', :some_date => date.to_s }
|
336
348
|
end
|
337
349
|
|
338
|
-
subject(:hash) { type.load(value)}
|
350
|
+
subject(:hash) { type.load(value) }
|
339
351
|
|
340
352
|
it 'loads' do
|
341
|
-
hash['a string'].
|
342
|
-
hash['1'].
|
343
|
-
hash[:some_date].
|
344
|
-
hash['defaulted'].
|
353
|
+
expect(hash['a string']).to eq('12')
|
354
|
+
expect(hash['1']).to eq(2)
|
355
|
+
expect(hash[:some_date]).to eq(date)
|
356
|
+
expect(hash['defaulted']).to eq('default value')
|
345
357
|
end
|
346
358
|
|
347
359
|
context 'with unknown keys in input' do
|
348
360
|
it 'raises an error' do
|
349
|
-
expect
|
350
|
-
type.load(
|
351
|
-
|
361
|
+
expect do
|
362
|
+
type.load('other_key' => :value)
|
363
|
+
end.to raise_error(Attributor::AttributorException)
|
352
364
|
end
|
353
365
|
end
|
354
366
|
|
355
|
-
|
356
367
|
context 'with a key_type' do
|
357
368
|
let(:block) do
|
358
369
|
proc do
|
@@ -365,16 +376,15 @@ describe Attributor::Hash do
|
|
365
376
|
|
366
377
|
subject(:type) { Attributor::Hash.of(key: String).construct(block) }
|
367
378
|
let(:value) do
|
368
|
-
{'a string' => 12, 1 => '2', :some_date => date.to_s}
|
379
|
+
{ 'a string' => 12, 1 => '2', :some_date => date.to_s }
|
369
380
|
end
|
370
381
|
|
371
382
|
it 'loads' do
|
372
|
-
hash['a string'].
|
373
|
-
hash['1'].
|
374
|
-
hash['some_date'].
|
375
|
-
hash['defaulted'].
|
383
|
+
expect(hash['a string']).to eq('12')
|
384
|
+
expect(hash['1']).to eq(2)
|
385
|
+
expect(hash['some_date']).to eq(date)
|
386
|
+
expect(hash['defaulted']).to eq('default value')
|
376
387
|
end
|
377
|
-
|
378
388
|
end
|
379
389
|
end
|
380
390
|
|
@@ -386,75 +396,73 @@ describe Attributor::Hash do
|
|
386
396
|
end
|
387
397
|
|
388
398
|
it 'raises an error' do
|
389
|
-
expect
|
390
|
-
Attributor::Hash.of(key:String).construct(block).keys
|
391
|
-
|
399
|
+
expect do
|
400
|
+
Attributor::Hash.of(key: String).construct(block).keys
|
401
|
+
end.to raise_error(/Invalid key: :some_date, must be instance of String/)
|
392
402
|
end
|
393
|
-
|
394
403
|
end
|
395
404
|
end
|
396
405
|
|
397
406
|
context '.check_option!' do
|
398
407
|
context ':case_insensitive_load' do
|
399
|
-
|
400
408
|
it 'is valid when key_type is a string' do
|
401
|
-
Attributor::Hash.of(key:String).check_option!(:case_insensitive_load, true).
|
409
|
+
expect(Attributor::Hash.of(key: String).check_option!(:case_insensitive_load, true)).to eq :ok
|
402
410
|
end
|
403
411
|
|
404
412
|
it 'is invalid when key_type is non-string' do
|
405
|
-
expect
|
406
|
-
Attributor::Hash.of(key:Integer).check_option!(:case_insensitive_load, true)
|
407
|
-
|
413
|
+
expect do
|
414
|
+
Attributor::Hash.of(key: Integer).check_option!(:case_insensitive_load, true)
|
415
|
+
end.to raise_error(Attributor::AttributorException,
|
416
|
+
/:case_insensitive_load may not be used/)
|
408
417
|
end
|
409
|
-
|
410
418
|
end
|
411
|
-
it 'rejects unknown options'
|
412
|
-
subject.check_option!(:bad_option, Object).
|
419
|
+
it 'rejects unknown options' do
|
420
|
+
expect(subject.check_option!(:bad_option, Object)).to eq :unknown
|
413
421
|
end
|
414
|
-
|
415
422
|
end
|
416
423
|
|
417
424
|
context '.add_requirement' do
|
418
|
-
let(:req_type){ :all }
|
419
|
-
let(:req){ double(
|
425
|
+
let(:req_type) { :all }
|
426
|
+
let(:req) { double('requirement', type: req_type, attr_names: req_attributes) }
|
420
427
|
context 'with valid attributes' do
|
421
|
-
let(:req_attributes){ [:name] }
|
428
|
+
let(:req_attributes) { [:name] }
|
422
429
|
it 'successfully saves it in the class' do
|
423
430
|
HashWithStrings.add_requirement(req)
|
424
|
-
HashWithStrings.requirements.
|
431
|
+
expect(HashWithStrings.requirements).to include(req)
|
425
432
|
end
|
426
433
|
end
|
427
434
|
context 'with attributes not defined in the class' do
|
428
|
-
let(:req_attributes){ [:name, :invalid, :notgood] }
|
435
|
+
let(:req_attributes) { [:name, :invalid, :notgood] }
|
429
436
|
it 'it complains loudly' do
|
430
|
-
expect
|
437
|
+
expect do
|
431
438
|
HashWithStrings.add_requirement(req)
|
432
|
-
|
439
|
+
end.to raise_error(
|
440
|
+
'Invalid attribute name(s) found (invalid, notgood) when defining a requirement of type all for HashWithStrings .The only existing attributes are [:name, :something]'
|
441
|
+
)
|
433
442
|
end
|
434
443
|
end
|
435
444
|
end
|
436
445
|
|
437
446
|
context '.dump' do
|
438
|
-
|
439
|
-
let(:value) { {one: 1, two: 2} }
|
447
|
+
let(:value) { { one: 1, two: 2 } }
|
440
448
|
let(:opts) { {} }
|
441
449
|
|
442
450
|
it 'it is Dumpable' do
|
443
|
-
type.new.is_a?(Attributor::Dumpable).
|
451
|
+
expect(type.new.is_a?(Attributor::Dumpable)).to be(true)
|
444
452
|
end
|
445
453
|
|
446
454
|
context 'for a simple (untyped) hash' do
|
447
455
|
it 'returns the untouched hash value' do
|
448
|
-
type.dump(value, opts).
|
456
|
+
expect(type.dump(value, opts)).to eq(value)
|
449
457
|
end
|
450
458
|
end
|
451
459
|
|
452
460
|
context 'for a typed hash' do
|
453
461
|
before do
|
454
|
-
subtype.
|
462
|
+
expect(subtype).to receive(:dump).exactly(2).times.and_call_original
|
455
463
|
end
|
456
|
-
let(:value1) { {first:
|
457
|
-
let(:value2) { {first:
|
464
|
+
let(:value1) { { first: 'Joe', last: 'Moe' } }
|
465
|
+
let(:value2) { { first: 'Mary', last: 'Foe' } }
|
458
466
|
let(:value) { { id1: subtype.new(value1), id2: subtype.new(value2) } }
|
459
467
|
let(:subtype) do
|
460
468
|
Class.new(Attributor::Model) do
|
@@ -468,11 +476,11 @@ describe Attributor::Hash do
|
|
468
476
|
|
469
477
|
it 'returns a hash with the dumped values and keys' do
|
470
478
|
dumped_value = type.dump(value, opts)
|
471
|
-
dumped_value.
|
472
|
-
dumped_value.keys.
|
473
|
-
dumped_value.values.
|
474
|
-
dumped_value['id1'].
|
475
|
-
dumped_value['id2'].
|
479
|
+
expect(dumped_value).to be_kind_of(::Hash)
|
480
|
+
expect(dumped_value.keys).to match_array %w(id1 id2)
|
481
|
+
expect(dumped_value.values).to have(2).items
|
482
|
+
expect(dumped_value['id1']).to eq value1
|
483
|
+
expect(dumped_value['id2']).to eq value2
|
476
484
|
end
|
477
485
|
|
478
486
|
context 'that has nil attribute values' do
|
@@ -480,31 +488,29 @@ describe Attributor::Hash do
|
|
480
488
|
|
481
489
|
it 'correctly returns nil rather than trying to dump their contents' do
|
482
490
|
dumped_value = type.dump(value, opts)
|
483
|
-
dumped_value.
|
484
|
-
dumped_value.keys.
|
485
|
-
dumped_value['id1'].
|
486
|
-
dumped_value['id2'].
|
491
|
+
expect(dumped_value).to be_kind_of(::Hash)
|
492
|
+
expect(dumped_value.keys).to match_array %w(id1 id2)
|
493
|
+
expect(dumped_value['id1']).to be nil
|
494
|
+
expect(dumped_value['id2']).to eq value2
|
487
495
|
end
|
488
496
|
end
|
489
497
|
end
|
490
|
-
|
491
498
|
end
|
492
499
|
|
493
500
|
context '#validate' do
|
494
501
|
context 'for a key and value typed hash' do
|
495
|
-
let(:key_type){ Integer }
|
496
|
-
let(:value_type){ DateTime }
|
502
|
+
let(:key_type) { Integer }
|
503
|
+
let(:value_type) { DateTime }
|
497
504
|
|
498
505
|
let(:type) { Attributor::Hash.of(key: key_type, value: value_type) }
|
499
506
|
subject(:hash) { type.new('one' => :two) }
|
500
507
|
|
501
508
|
it 'returns errors for key and value' do
|
502
509
|
errors = hash.validate
|
503
|
-
errors.
|
504
|
-
|
505
|
-
errors.should include("Attribute $.key(\"one\") received value: \"one\" is of the wrong type (got: String, expected: Attributor::Integer)")
|
506
|
-
errors.should include("Attribute $.value(:two) received value: :two is of the wrong type (got: Symbol, expected: Attributor::DateTime)")
|
510
|
+
expect(errors).to have(2).items
|
507
511
|
|
512
|
+
expect(errors).to include('Attribute $.key("one") received value: "one" is of the wrong type (got: String, expected: Attributor::Integer)')
|
513
|
+
expect(errors).to include('Attribute $.value(:two) received value: :two is of the wrong type (got: Symbol, expected: Attributor::DateTime)')
|
508
514
|
end
|
509
515
|
end
|
510
516
|
|
@@ -519,18 +525,16 @@ describe Attributor::Hash do
|
|
519
525
|
|
520
526
|
let(:type) { Attributor::Hash.construct(block) }
|
521
527
|
|
522
|
-
let(:values) { {'integer' => 'one', 'datetime' => 'now' } }
|
528
|
+
let(:values) { { 'integer' => 'one', 'datetime' => 'now' } }
|
523
529
|
subject(:hash) { type.new(values) }
|
524
530
|
|
525
531
|
it 'validates the keys' do
|
526
532
|
errors = hash.validate
|
527
|
-
errors.
|
528
|
-
errors.
|
533
|
+
expect(errors).to have(3).items
|
534
|
+
expect(errors).to include('Attribute $.key("not-optional") is required')
|
529
535
|
end
|
530
|
-
|
531
536
|
end
|
532
537
|
|
533
|
-
|
534
538
|
context 'with requirements defined' do
|
535
539
|
let(:type) { Attributor::Hash.construct(block) }
|
536
540
|
|
@@ -548,9 +552,9 @@ describe Attributor::Hash do
|
|
548
552
|
|
549
553
|
it 'complains not all the listed elements are set (false or true)' do
|
550
554
|
errors = type.new('name' => 'CAP').validate
|
551
|
-
errors.
|
552
|
-
|
553
|
-
errors.
|
555
|
+
expect(errors).to have(2).items
|
556
|
+
%w(consistency availability).each do |name|
|
557
|
+
expect(errors).to include("Key #{name} is required for $.")
|
554
558
|
end
|
555
559
|
end
|
556
560
|
end
|
@@ -568,9 +572,9 @@ describe Attributor::Hash do
|
|
568
572
|
|
569
573
|
it 'complains if less than 2 in the group are set (false or true)' do
|
570
574
|
errors = type.new('name' => 'CAP', 'consistency' => false).validate
|
571
|
-
errors.
|
572
|
-
errors.
|
573
|
-
|
575
|
+
expect(errors).to have(1).items
|
576
|
+
expect(errors).to include(
|
577
|
+
'At least 2 keys out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found ["consistency"]'
|
574
578
|
)
|
575
579
|
end
|
576
580
|
end
|
@@ -588,8 +592,8 @@ describe Attributor::Hash do
|
|
588
592
|
|
589
593
|
it 'complains if more than 2 in the group are set (false or true)' do
|
590
594
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true, 'partitioning' => false).validate
|
591
|
-
errors.
|
592
|
-
errors.
|
595
|
+
expect(errors).to have(1).items
|
596
|
+
expect(errors).to include('At most 2 keys out of ["consistency", "availability", "partitioning"] can be passed in for $. Found ["consistency", "availability", "partitioning"]')
|
593
597
|
end
|
594
598
|
end
|
595
599
|
|
@@ -606,13 +610,13 @@ describe Attributor::Hash do
|
|
606
610
|
|
607
611
|
it 'complains if less than 1 in the group are set (false or true)' do
|
608
612
|
errors = type.new('name' => 'CAP').validate
|
609
|
-
errors.
|
610
|
-
errors.
|
613
|
+
expect(errors).to have(1).items
|
614
|
+
expect(errors).to include('Exactly 1 of the following keys ["consistency", "availability", "partitioning"] are required for $. Found 0 instead: []')
|
611
615
|
end
|
612
616
|
it 'complains if more than 1 in the group are set (false or true)' do
|
613
617
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
614
|
-
errors.
|
615
|
-
errors.
|
618
|
+
expect(errors).to have(1).items
|
619
|
+
expect(errors).to include('Exactly 1 of the following keys ["consistency", "availability", "partitioning"] are required for $. Found 2 instead: ["consistency", "availability"]')
|
616
620
|
end
|
617
621
|
end
|
618
622
|
|
@@ -629,8 +633,8 @@ describe Attributor::Hash do
|
|
629
633
|
|
630
634
|
it 'complains if two or more in the group are set (false or true)' do
|
631
635
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
632
|
-
errors.
|
633
|
-
errors.
|
636
|
+
expect(errors).to have(1).items
|
637
|
+
expect(errors).to include('keys ["consistency", "availability"] are mutually exclusive for $.')
|
634
638
|
end
|
635
639
|
end
|
636
640
|
|
@@ -653,38 +657,36 @@ describe Attributor::Hash do
|
|
653
657
|
|
654
658
|
it 'complains not all the listed elements are set (false or true)' do
|
655
659
|
errors = type.new('name' => 'CAP').validate
|
656
|
-
errors.
|
657
|
-
errors.
|
658
|
-
|
660
|
+
expect(errors).to have(1).items
|
661
|
+
expect(errors).to include(
|
662
|
+
'At least 1 keys out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found none'
|
659
663
|
)
|
660
664
|
end
|
661
665
|
end
|
662
666
|
end
|
663
|
-
|
664
667
|
end
|
665
668
|
|
666
669
|
context 'in an Attribute' do
|
667
670
|
let(:options) { {} }
|
668
|
-
subject(:attribute) { Attributor::Attribute.new(Attributor::Hash, options)}
|
671
|
+
subject(:attribute) { Attributor::Attribute.new(Attributor::Hash, options) }
|
669
672
|
|
670
673
|
context 'with an example option that is a proc' do
|
671
|
-
let(:example_hash) { {:
|
674
|
+
let(:example_hash) { { key: 'value' } }
|
672
675
|
let(:options) { { example: proc { example_hash } } }
|
673
676
|
it 'uses the hash' do
|
674
|
-
attribute.example.
|
677
|
+
expect(attribute.example).to eq(example_hash)
|
675
678
|
end
|
676
679
|
end
|
677
|
-
|
678
680
|
end
|
679
681
|
|
680
682
|
context '.describe' do
|
681
|
-
let(:example){ nil }
|
683
|
+
let(:example) { nil }
|
682
684
|
subject(:description) { type.describe(example: example) }
|
683
685
|
context 'for hashes with key and value types' do
|
684
686
|
it 'describes the type correctly' do
|
685
|
-
description[:name].
|
686
|
-
description[:key].
|
687
|
-
description[:value].
|
687
|
+
expect(description[:name]).to eq('Hash')
|
688
|
+
expect(description[:key]).to eq(type: { name: 'Object', id: 'Attributor-Object', family: 'any' })
|
689
|
+
expect(description[:value]).to eq(type: { name: 'Object', id: 'Attributor-Object', family: 'any' })
|
688
690
|
end
|
689
691
|
end
|
690
692
|
|
@@ -696,7 +698,7 @@ describe Attributor::Hash do
|
|
696
698
|
key 'some_date', DateTime
|
697
699
|
key 'defaulted', String, default: 'default value'
|
698
700
|
requires do
|
699
|
-
all.of '1','some_date'
|
701
|
+
all.of '1', 'some_date'
|
700
702
|
exclusive 'some_date', 'defaulted'
|
701
703
|
at_least(1).of 'a string', 'some_date'
|
702
704
|
at_most(2).of 'a string', 'some_date'
|
@@ -708,30 +710,29 @@ describe Attributor::Hash do
|
|
708
710
|
let(:type) { Attributor::Hash.of(key: String).construct(block) }
|
709
711
|
|
710
712
|
it 'describes the type correctly' do
|
711
|
-
description[:name].
|
712
|
-
description[:key].
|
713
|
-
description.
|
713
|
+
expect(description[:name]).to eq('Hash')
|
714
|
+
expect(description[:key]).to eq(type: { name: 'String', id: 'Attributor-String', family: 'string' })
|
715
|
+
expect(description).not_to have_key(:value)
|
714
716
|
end
|
715
717
|
|
716
718
|
it 'describes the type attributes correctly' do
|
717
719
|
attrs = description[:attributes]
|
718
720
|
|
719
|
-
attrs['a string'].
|
720
|
-
attrs['1'].
|
721
|
-
attrs['some_date'].
|
722
|
-
attrs['defaulted'].
|
721
|
+
expect(attrs['a string']).to eq(type: { name: 'String', id: 'Attributor-String', family: 'string' })
|
722
|
+
expect(attrs['1']).to eq(type: { name: 'Integer', id: 'Attributor-Integer', family: 'numeric' }, options: { min: 1, max: 20 })
|
723
|
+
expect(attrs['some_date']).to eq(type: { name: 'DateTime', id: 'Attributor-DateTime', family: 'temporal' })
|
724
|
+
expect(attrs['defaulted']).to eq(type: { name: 'String', id: 'Attributor-String', family: 'string' }, default: 'default value')
|
723
725
|
end
|
724
726
|
|
725
727
|
it 'describes the type requirements correctly' do
|
726
|
-
|
727
728
|
reqs = description[:requirements]
|
728
|
-
reqs.
|
729
|
-
reqs.size.
|
730
|
-
reqs.
|
731
|
-
reqs.
|
732
|
-
reqs.
|
733
|
-
reqs.
|
734
|
-
reqs.
|
729
|
+
expect(reqs).to be_kind_of(Array)
|
730
|
+
expect(reqs.size).to be(5)
|
731
|
+
expect(reqs).to include(type: :all, attributes: %w(1 some_date))
|
732
|
+
expect(reqs).to include(type: :exclusive, attributes: %w(some_date defaulted))
|
733
|
+
expect(reqs).to include(type: :at_least, attributes: ['a string', 'some_date'], count: 1)
|
734
|
+
expect(reqs).to include(type: :at_most, attributes: ['a string', 'some_date'], count: 2)
|
735
|
+
expect(reqs).to include(type: :exactly, attributes: ['a string', 'some_date'], count: 1)
|
735
736
|
end
|
736
737
|
|
737
738
|
context 'merging requires.all with attribute required: true' do
|
@@ -746,8 +747,8 @@ describe Attributor::Hash do
|
|
746
747
|
end
|
747
748
|
end
|
748
749
|
it 'includes attributes with required: true into the :all requirements' do
|
749
|
-
req_all = description[:requirements].select{|r| r[:type] == :all}.first
|
750
|
-
req_all[:attributes].
|
750
|
+
req_all = description[:requirements].select { |r| r[:type] == :all }.first
|
751
|
+
expect(req_all[:attributes]).to include('required string', 'some_date')
|
751
752
|
end
|
752
753
|
end
|
753
754
|
|
@@ -759,21 +760,21 @@ describe Attributor::Hash do
|
|
759
760
|
end
|
760
761
|
end
|
761
762
|
it 'includes attributes with required: true into the :all requirements' do
|
762
|
-
req_all = description[:requirements].select{|r| r[:type] == :all}.first
|
763
|
-
req_all.
|
764
|
-
req_all[:attributes].
|
763
|
+
req_all = description[:requirements].select { |r| r[:type] == :all }.first
|
764
|
+
expect(req_all).not_to be(nil)
|
765
|
+
expect(req_all[:attributes]).to include('required string', 'required integer')
|
765
766
|
end
|
766
767
|
end
|
767
768
|
|
768
769
|
context 'with an example' do
|
769
|
-
let(:example){ type.example }
|
770
|
+
let(:example) { type.example }
|
770
771
|
|
771
772
|
it 'should have the matching example for each leaf key' do
|
772
|
-
description[:attributes].keys.
|
773
|
-
description[:attributes].each do |name,sub_description|
|
774
|
-
sub_description.
|
773
|
+
expect(description[:attributes].keys).to match_array type.keys.keys
|
774
|
+
description[:attributes].each do |name, sub_description|
|
775
|
+
expect(sub_description).to have_key(:example)
|
775
776
|
val = type.attributes[name].dump(example[name])
|
776
|
-
sub_description[:example].
|
777
|
+
expect(sub_description[:example]).to eq val
|
777
778
|
end
|
778
779
|
end
|
779
780
|
end
|
@@ -781,17 +782,17 @@ describe Attributor::Hash do
|
|
781
782
|
end
|
782
783
|
|
783
784
|
context '#dump' do
|
784
|
-
let(:key_type){ String }
|
785
|
-
let(:value_type){ Integer }
|
786
|
-
let(:hash) { {one: '2', 3 => 4} }
|
785
|
+
let(:key_type) { String }
|
786
|
+
let(:value_type) { Integer }
|
787
|
+
let(:hash) { { one: '2', 3 => 4 } }
|
787
788
|
let(:type) { Attributor::Hash.of(key: key_type, value: value_type) }
|
788
789
|
let(:value) { type.load(hash) }
|
789
790
|
|
790
791
|
subject(:output) { value.dump }
|
791
792
|
|
792
793
|
it 'dumps the contents properly' do
|
793
|
-
output.
|
794
|
-
output.
|
794
|
+
expect(output).to be_kind_of(::Hash)
|
795
|
+
expect(output).to eq('one' => 2, '3' => 4)
|
795
796
|
end
|
796
797
|
|
797
798
|
context 'with a model as value type' do
|
@@ -803,17 +804,16 @@ describe Attributor::Hash do
|
|
803
804
|
end
|
804
805
|
end
|
805
806
|
end
|
806
|
-
let(:hash) { {one: value_type.example} }
|
807
|
-
|
807
|
+
let(:hash) { { one: value_type.example } }
|
808
808
|
end
|
809
809
|
context 'will always return a top level hash' do
|
810
|
-
subject(:type_dump){ type.dump(value) }
|
811
|
-
let(:key_type){ Attributor::Object }
|
812
|
-
let(:value_type){ Attributor::Object }
|
810
|
+
subject(:type_dump) { type.dump(value) }
|
811
|
+
let(:key_type) { Attributor::Object }
|
812
|
+
let(:value_type) { Attributor::Object }
|
813
813
|
|
814
814
|
it 'even when key/types are object' do
|
815
|
-
subject.
|
816
|
-
subject.
|
815
|
+
expect(subject).to be_kind_of(::Hash)
|
816
|
+
expect(subject).to eq(hash)
|
817
817
|
end
|
818
818
|
end
|
819
819
|
|
@@ -827,13 +827,13 @@ describe Attributor::Hash do
|
|
827
827
|
end
|
828
828
|
end
|
829
829
|
|
830
|
-
let(:chicken) { {'name' => 'bob'} }
|
830
|
+
let(:chicken) { { 'name' => 'bob' } }
|
831
831
|
|
832
|
-
let(:value) { {'id' => '1', 'chicken' => chicken
|
833
|
-
let(:expected) { {'id' => 1, 'chicken' => Chicken.dump(chicken) } }
|
832
|
+
let(:value) { { 'id' => '1', 'chicken' => chicken } }
|
833
|
+
let(:expected) { { 'id' => 1, 'chicken' => Chicken.dump(chicken) } }
|
834
834
|
|
835
835
|
it 'properly dumps the values' do
|
836
|
-
type.dump(value).
|
836
|
+
expect(type.dump(value)).to eq(expected)
|
837
837
|
end
|
838
838
|
|
839
839
|
context 'with allow_extra: true' do
|
@@ -846,10 +846,10 @@ describe Attributor::Hash do
|
|
846
846
|
end
|
847
847
|
end
|
848
848
|
|
849
|
-
let(:value) { {'id' => '1', 'chicken' => chicken, 'rank' => 'bob rank'} }
|
850
|
-
let(:expected) { {'id' => 1, 'chicken' => Chicken.dump(chicken), 'rank' => 'bob rank' } }
|
849
|
+
let(:value) { { 'id' => '1', 'chicken' => chicken, 'rank' => 'bob rank' } }
|
850
|
+
let(:expected) { { 'id' => 1, 'chicken' => Chicken.dump(chicken), 'rank' => 'bob rank' } }
|
851
851
|
it 'preserves the extra keys at the top level' do
|
852
|
-
type.dump(value).
|
852
|
+
expect(type.dump(value)).to eq(expected)
|
853
853
|
end
|
854
854
|
|
855
855
|
context 'with extra option' do
|
@@ -863,9 +863,9 @@ describe Attributor::Hash do
|
|
863
863
|
end
|
864
864
|
end
|
865
865
|
|
866
|
-
let(:expected) { {'id' => 1, 'chicken' => Chicken.dump(chicken), 'other' => {'rank' => 'bob rank' }} }
|
866
|
+
let(:expected) { { 'id' => 1, 'chicken' => Chicken.dump(chicken), 'other' => { 'rank' => 'bob rank' } } }
|
867
867
|
it 'dumps the extra keys inside the subhash' do
|
868
|
-
type.dump(value).
|
868
|
+
expect(type.dump(value)).to eq(expected)
|
869
869
|
end
|
870
870
|
end
|
871
871
|
end
|
@@ -873,7 +873,6 @@ describe Attributor::Hash do
|
|
873
873
|
end
|
874
874
|
|
875
875
|
context '.from_hash' do
|
876
|
-
|
877
876
|
context 'without allowing extra keys' do
|
878
877
|
let(:type) do
|
879
878
|
Class.new(Attributor::Hash) do
|
@@ -885,29 +884,29 @@ describe Attributor::Hash do
|
|
885
884
|
end
|
886
885
|
end
|
887
886
|
end
|
888
|
-
subject(:input){ {} }
|
887
|
+
subject(:input) { {} }
|
889
888
|
subject(:output) { type.load(input) }
|
890
889
|
|
891
|
-
its(:class){ should be(type) }
|
890
|
+
its(:class) { should be(type) }
|
892
891
|
|
893
|
-
let(:load_context) {
|
892
|
+
let(:load_context) { '$.some_root' }
|
894
893
|
it 'complains about the extra, with the right context' do
|
895
|
-
expect
|
896
|
-
type.load(
|
897
|
-
|
894
|
+
expect do
|
895
|
+
type.load({ one: 'one', three: 3 }, load_context)
|
896
|
+
end.to raise_error(Attributor::AttributorException, /Unknown key received: :three while loading \$\.some_root.key\(:three\)/)
|
898
897
|
end
|
899
898
|
context 'properly sets them (and loads them) in the created instance' do
|
900
|
-
let(:input){ {one: 'one', two: 2 } }
|
899
|
+
let(:input) { { one: 'one', two: 2 } }
|
901
900
|
|
902
|
-
its(:keys){ should eq([:one, :two])}
|
903
|
-
its([:one]){ should eq('one') }
|
904
|
-
its([:two]){ should eq('2') } #loaded as a string
|
901
|
+
its(:keys) { should eq([:one, :two]) }
|
902
|
+
its([:one]) { should eq('one') }
|
903
|
+
its([:two]) { should eq('2') } # loaded as a string
|
905
904
|
end
|
906
905
|
context 'properly sets the default values when not passed in' do
|
907
|
-
let(:input){ {one: 'one'} }
|
906
|
+
let(:input) { { one: 'one' } }
|
908
907
|
|
909
|
-
its([:one]){ should eq('one') }
|
910
|
-
its([:two]){ should eq('two') }
|
908
|
+
its([:one]) { should eq('one') }
|
909
|
+
its([:two]) { should eq('two') }
|
911
910
|
end
|
912
911
|
end
|
913
912
|
|
@@ -920,10 +919,10 @@ describe Attributor::Hash do
|
|
920
919
|
end
|
921
920
|
end
|
922
921
|
end
|
923
|
-
let(:input){ {one: 'one', three: 'tres' } }
|
922
|
+
let(:input) { { one: 'one', three: 'tres' } }
|
924
923
|
subject(:output) { type.load(input) }
|
925
924
|
|
926
|
-
its(:keys){ should eq([:one
|
925
|
+
its(:keys) { should eq([:one, :three]) }
|
927
926
|
end
|
928
927
|
context 'inside an :other subkey' do
|
929
928
|
let(:type) do
|
@@ -934,12 +933,12 @@ describe Attributor::Hash do
|
|
934
933
|
end
|
935
934
|
end
|
936
935
|
end
|
937
|
-
let(:input){ {one: 'one', three: 'tres' } }
|
936
|
+
let(:input) { { one: 'one', three: 'tres' } }
|
938
937
|
subject(:output) { type.load(input) }
|
939
938
|
|
940
|
-
its(:keys){ should
|
939
|
+
its(:keys) { should match_array [:one, :other] }
|
941
940
|
it 'has the key inside the :other hash' do
|
942
|
-
expect(output[:other]).to eq(
|
941
|
+
expect(output[:other]).to eq(three: 'tres')
|
943
942
|
end
|
944
943
|
end
|
945
944
|
end
|
@@ -955,20 +954,19 @@ describe Attributor::Hash do
|
|
955
954
|
key 'CamelCase', Integer
|
956
955
|
end
|
957
956
|
end
|
958
|
-
let(:input) { {'DOWNCASE' => 1, 'upcase' => 2, 'CamelCase' => 3} }
|
957
|
+
let(:input) { { 'DOWNCASE' => 1, 'upcase' => 2, 'CamelCase' => 3 } }
|
959
958
|
subject(:output) { type.load(input) }
|
960
959
|
|
961
960
|
context 'when defined' do
|
962
|
-
|
963
961
|
it 'maps the incoming keys to defined keys, regardless of case' do
|
964
|
-
output['downcase'].
|
965
|
-
output['UPCASE'].
|
966
|
-
output['CamelCase'].
|
962
|
+
expect(output['downcase']).to eq(1)
|
963
|
+
expect(output['UPCASE']).to eq(2)
|
964
|
+
expect(output['CamelCase']).to eq(3)
|
967
965
|
end
|
968
966
|
it 'has loaded the (internal) insensitive_map upon building the definition' do
|
969
967
|
type.definition
|
970
|
-
type.insensitive_map.
|
971
|
-
type.insensitive_map.keys.
|
968
|
+
expect(type.insensitive_map).to be_kind_of(::Hash)
|
969
|
+
expect(type.insensitive_map.keys).to match_array %w(downcase upcase camelcase)
|
972
970
|
end
|
973
971
|
end
|
974
972
|
|
@@ -977,7 +975,7 @@ describe Attributor::Hash do
|
|
977
975
|
|
978
976
|
it 'skips the loading of the (internal) insensitive_map' do
|
979
977
|
type.definition
|
980
|
-
type.insensitive_map.
|
978
|
+
expect(type.insensitive_map).to be_nil
|
981
979
|
end
|
982
980
|
end
|
983
981
|
end
|
@@ -994,19 +992,19 @@ describe Attributor::Hash do
|
|
994
992
|
end
|
995
993
|
end
|
996
994
|
|
997
|
-
let(:input) { {one: 'one', three: 3} }
|
995
|
+
let(:input) { { one: 'one', three: 3 } }
|
998
996
|
subject(:output) { type.load(input) }
|
999
997
|
|
1000
998
|
context 'that should be saved at the top level' do
|
1001
|
-
its(:keys) { should
|
999
|
+
its(:keys) { should match_array [:one, :two, :three] }
|
1002
1000
|
|
1003
1001
|
it 'loads the extra keys' do
|
1004
|
-
output[:one].
|
1005
|
-
output[:two].
|
1006
|
-
output[:three].
|
1002
|
+
expect(output[:one]).to eq('one')
|
1003
|
+
expect(output[:two]).to eq('two')
|
1004
|
+
expect(output[:three]).to eq('3')
|
1007
1005
|
end
|
1008
1006
|
|
1009
|
-
its(
|
1007
|
+
its(:validate) { should be_empty }
|
1010
1008
|
end
|
1011
1009
|
|
1012
1010
|
context 'that should be grouped into a sub-hash' do
|
@@ -1016,25 +1014,24 @@ describe Attributor::Hash do
|
|
1016
1014
|
end
|
1017
1015
|
end
|
1018
1016
|
|
1019
|
-
its(:keys) { should
|
1017
|
+
its(:keys) { should match_array [:one, :two, :options] }
|
1020
1018
|
it 'loads the extra keys into :options sub-hash' do
|
1021
|
-
output[:one].
|
1022
|
-
output[:two].
|
1023
|
-
output[:options].
|
1019
|
+
expect(output[:one]).to eq('one')
|
1020
|
+
expect(output[:two]).to eq('two')
|
1021
|
+
expect(output[:options]).to eq(three: 3)
|
1024
1022
|
end
|
1025
|
-
its(
|
1023
|
+
its(:validate) { should be_empty }
|
1026
1024
|
|
1027
1025
|
context 'with an options value already provided' do
|
1028
|
-
its(:keys) { should
|
1029
|
-
let(:input) { {one: 'one', three: 3, options: {four: 4}} }
|
1026
|
+
its(:keys) { should match_array [:one, :two, :options] }
|
1027
|
+
let(:input) { { one: 'one', three: 3, options: { four: 4 } } }
|
1030
1028
|
|
1031
1029
|
it 'loads the extra keys into :options sub-hash' do
|
1032
|
-
output[:one].
|
1033
|
-
output[:two].
|
1034
|
-
output[:options].
|
1030
|
+
expect(output[:one]).to eq('one')
|
1031
|
+
expect(output[:two]).to eq('two')
|
1032
|
+
expect(output[:options]).to eq(three: 3, four: 4)
|
1035
1033
|
end
|
1036
|
-
its(
|
1037
|
-
|
1034
|
+
its(:validate) { should be_empty }
|
1038
1035
|
end
|
1039
1036
|
end
|
1040
1037
|
|
@@ -1049,53 +1046,50 @@ describe Attributor::Hash do
|
|
1049
1046
|
end
|
1050
1047
|
end
|
1051
1048
|
|
1052
|
-
let(:chicken) { {name: 'bob'} }
|
1049
|
+
let(:chicken) { { name: 'bob' } }
|
1053
1050
|
subject(:hash) { type.new }
|
1054
1051
|
|
1055
1052
|
context '#set' do
|
1056
1053
|
it 'sets values into "extra" keys if appplicable' do
|
1057
|
-
hash.
|
1054
|
+
expect(hash).not_to have_key('others')
|
1058
1055
|
hash.set 'foo', 'bar'
|
1059
|
-
hash['others'].
|
1060
|
-
hash.
|
1061
|
-
hash['others']['foo'].
|
1056
|
+
expect(hash['others']).to have_key('foo')
|
1057
|
+
expect(hash).to have_key('others')
|
1058
|
+
expect(hash['others']['foo']).to eq('bar')
|
1062
1059
|
end
|
1063
1060
|
|
1064
1061
|
it 'loads values before saving into the contents' do
|
1065
1062
|
hash.set 'chicken', chicken
|
1066
|
-
hash['chicken'].
|
1063
|
+
expect(hash['chicken']).to be_a(Chicken)
|
1067
1064
|
end
|
1068
1065
|
end
|
1069
1066
|
|
1070
1067
|
context '#get' do
|
1071
1068
|
before do
|
1072
1069
|
hash['chicken'] = chicken
|
1073
|
-
hash['chicken'].
|
1070
|
+
expect(hash['chicken']).to eq(chicken)
|
1074
1071
|
end
|
1075
1072
|
|
1076
1073
|
it 'loads and updates the saved value' do
|
1077
|
-
hash.get('chicken').
|
1078
|
-
hash['chicken'].
|
1074
|
+
expect(hash.get('chicken')).to be_a(Chicken)
|
1075
|
+
expect(hash['chicken']).to be_a(Chicken)
|
1079
1076
|
end
|
1080
1077
|
|
1081
1078
|
it 'retrieves values from an "extra" key' do
|
1082
1079
|
bar = double('bar')
|
1083
1080
|
hash.set 'foo', bar
|
1084
|
-
hash.get('others').get('foo').
|
1081
|
+
expect(hash.get('others').get('foo')).to be(bar)
|
1085
1082
|
|
1086
|
-
hash.get('foo').
|
1083
|
+
expect(hash.get('foo')).to be(bar)
|
1087
1084
|
end
|
1088
1085
|
|
1089
1086
|
it 'does not set a key that is unset' do
|
1090
|
-
hash.
|
1091
|
-
hash.get('id').
|
1092
|
-
hash.
|
1087
|
+
expect(hash).not_to have_key('id')
|
1088
|
+
expect(hash.get('id')).to be(nil)
|
1089
|
+
expect(hash).not_to have_key('id')
|
1093
1090
|
end
|
1094
|
-
|
1095
1091
|
end
|
1096
|
-
|
1097
1092
|
end
|
1098
|
-
|
1099
1093
|
end
|
1100
1094
|
|
1101
1095
|
context '#merge' do
|
@@ -1120,6 +1114,8 @@ describe Attributor::Hash do
|
|
1120
1114
|
it 'merges' do
|
1121
1115
|
expect(merger.merge(good_mergee)).to eq(result)
|
1122
1116
|
end
|
1117
|
+
end
|
1123
1118
|
|
1119
|
+
context Attributor::InvalidDefinition do
|
1124
1120
|
end
|
1125
1121
|
end
|