hashie 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -187
  3. data/CONTRIBUTING.md +13 -6
  4. data/README.md +33 -9
  5. data/UPGRADING.md +5 -5
  6. data/hashie.gemspec +11 -6
  7. data/lib/hashie.rb +1 -0
  8. data/lib/hashie/extensions/dash/property_translation.rb +1 -1
  9. data/lib/hashie/extensions/deep_merge.rb +18 -1
  10. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  11. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +19 -2
  12. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  13. data/lib/hashie/mash.rb +31 -26
  14. data/lib/hashie/utils.rb +28 -0
  15. data/lib/hashie/version.rb +1 -1
  16. metadata +16 -131
  17. data/spec/hashie/array_spec.rb +0 -29
  18. data/spec/hashie/clash_spec.rb +0 -70
  19. data/spec/hashie/dash_spec.rb +0 -608
  20. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  21. data/spec/hashie/extensions/coercion_spec.rb +0 -648
  22. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  23. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  24. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  25. data/spec/hashie/extensions/deep_find_spec.rb +0 -144
  26. data/spec/hashie/extensions/deep_locate_spec.rb +0 -138
  27. data/spec/hashie/extensions/deep_merge_spec.rb +0 -74
  28. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -48
  29. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
  30. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  31. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  32. data/spec/hashie/extensions/mash/define_accessors_spec.rb +0 -90
  33. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  34. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  35. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  36. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  37. data/spec/hashie/extensions/method_access_spec.rb +0 -233
  38. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -109
  39. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  40. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -131
  41. data/spec/hashie/hash_spec.rb +0 -123
  42. data/spec/hashie/mash_spec.rb +0 -1077
  43. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  44. data/spec/hashie/rash_spec.rb +0 -83
  45. data/spec/hashie/trash_spec.rb +0 -334
  46. data/spec/hashie/utils_spec.rb +0 -25
  47. data/spec/hashie/version_spec.rb +0 -7
  48. data/spec/hashie_spec.rb +0 -13
  49. data/spec/integration/elasticsearch/integration_spec.rb +0 -41
  50. data/spec/integration/omniauth-oauth2/app.rb +0 -52
  51. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  52. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  53. data/spec/integration/omniauth/app.rb +0 -11
  54. data/spec/integration/omniauth/integration_spec.rb +0 -38
  55. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  56. data/spec/integration/rails/app.rb +0 -40
  57. data/spec/integration/rails/integration_spec.rb +0 -47
  58. data/spec/spec_helper.rb +0 -23
  59. data/spec/support/integration_specs.rb +0 -36
  60. data/spec/support/logger.rb +0 -24
  61. data/spec/support/module_context.rb +0 -11
  62. data/spec/support/ruby_version_check.rb +0 -6
@@ -1,1077 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Mash do
4
- subject { Hashie::Mash.new }
5
-
6
- include_context 'with a logger'
7
-
8
- it 'inherits from Hash' do
9
- expect(subject.is_a?(Hash)).to be_truthy
10
- end
11
-
12
- it 'sets hash values through method= calls' do
13
- subject.test = 'abc'
14
- expect(subject['test']).to eq 'abc'
15
- end
16
-
17
- it 'retrieves set values through method calls' do
18
- subject['test'] = 'abc'
19
- expect(subject.test).to eq 'abc'
20
- end
21
-
22
- it 'retrieves set values through blocks' do
23
- subject['test'] = 'abc'
24
- value = nil
25
- subject.[]('test') { |v| value = v }
26
- expect(value).to eq 'abc'
27
- end
28
-
29
- it 'retrieves set values through blocks with method calls' do
30
- subject['test'] = 'abc'
31
- value = nil
32
- subject.test { |v| value = v }
33
- expect(value).to eq 'abc'
34
- end
35
-
36
- it 'tests for already set values when passed a ? method' do
37
- expect(subject.test?).to be_falsy
38
- subject.test = 'abc'
39
- expect(subject.test?).to be_truthy
40
- end
41
-
42
- it 'returns false on a ? method if a value has been set to nil or false' do
43
- subject.test = nil
44
- expect(subject).not_to be_test
45
- subject.test = false
46
- expect(subject).not_to be_test
47
- end
48
-
49
- it 'makes all [] and []= into strings for consistency' do
50
- subject['abc'] = 123
51
- expect(subject.key?('abc')).to be_truthy
52
- expect(subject['abc']).to eq 123
53
- end
54
-
55
- it 'has a to_s that is identical to its inspect' do
56
- subject.abc = 123
57
- expect(subject.to_s).to eq subject.inspect
58
- end
59
-
60
- it 'returns nil instead of raising an error for attribute-esque method calls' do
61
- expect(subject.abc).to be_nil
62
- end
63
-
64
- it 'returns the default value if set like Hash' do
65
- subject.default = 123
66
- expect(subject.abc).to eq 123
67
- end
68
-
69
- it 'gracefully handles being accessed with arguments' do
70
- expect(subject.abc('foobar')).to eq nil
71
- subject.abc = 123
72
- expect(subject.abc('foobar')).to eq 123
73
- end
74
-
75
- # Added due to downstream gems assuming indifferent access to be true for Mash
76
- # When this is not, bump major version so that downstream gems can target
77
- # correct version and fix accordingly.
78
- # See https://github.com/intridea/hashie/pull/197
79
- it 'maintains indifferent access when nested' do
80
- subject[:a] = { b: 'c' }
81
- expect(subject[:a][:b]).to eq 'c'
82
- expect(subject[:a]['b']).to eq 'c'
83
- end
84
-
85
- it 'returns a Hashie::Mash when passed a bang method to a non-existenct key' do
86
- expect(subject.abc!.is_a?(Hashie::Mash)).to be_truthy
87
- end
88
-
89
- it 'returns the existing value when passed a bang method for an existing key' do
90
- subject.name = 'Bob'
91
- expect(subject.name!).to eq 'Bob'
92
- end
93
-
94
- it 'returns a Hashie::Mash when passed an under bang method to a non-existenct key' do
95
- expect(subject.abc_.is_a?(Hashie::Mash)).to be_truthy
96
- end
97
-
98
- it 'returns the existing value when passed an under bang method for an existing key' do
99
- subject.name = 'Bob'
100
- expect(subject.name_).to eq 'Bob'
101
- end
102
-
103
- it '#initializing_reader returns a Hashie::Mash when passed a non-existent key' do
104
- expect(subject.initializing_reader(:abc).is_a?(Hashie::Mash)).to be_truthy
105
- end
106
-
107
- it 'allows for multi-level assignment through bang methods' do
108
- subject.author!.name = 'Michael Bleigh'
109
- expect(subject.author).to eq Hashie::Mash.new(name: 'Michael Bleigh')
110
- subject.author!.website!.url = 'http://www.mbleigh.com/'
111
- expect(subject.author.website).to eq Hashie::Mash.new(url: 'http://www.mbleigh.com/')
112
- end
113
-
114
- it 'allows for multi-level under bang testing' do
115
- expect(subject.author_.website_.url).to be_nil
116
- expect(subject.author_.website_.url?).to eq false
117
- expect(subject.author).to be_nil
118
- end
119
-
120
- it 'does not call super if id is not a key' do
121
- expect(subject.id).to eq nil
122
- end
123
-
124
- it 'returns the value if id is a key' do
125
- subject.id = 'Steve'
126
- expect(subject.id).to eq 'Steve'
127
- end
128
-
129
- it 'does not call super if type is not a key' do
130
- expect(subject.type).to eq nil
131
- end
132
-
133
- it 'returns the value if type is a key' do
134
- subject.type = 'Steve'
135
- expect(subject.type).to eq 'Steve'
136
- end
137
-
138
- include_context 'with a logger' do
139
- it 'logs a warning when overriding built-in methods' do
140
- Hashie::Mash.new('trust' => { 'two' => 2 })
141
-
142
- expect(logger_output).to match('Hashie::Mash#trust')
143
- end
144
-
145
- it 'can set keys more than once and does not warn when doing so' do
146
- mash = Hashie::Mash.new
147
- mash[:test_key] = 'Test value'
148
-
149
- expect { mash[:test_key] = 'A new value' }.not_to raise_error
150
- expect(logger_output).to be_blank
151
- end
152
-
153
- it 'does not write to the logger when warnings are disabled' do
154
- mash_class = Class.new(Hashie::Mash) do
155
- disable_warnings
156
- end
157
- mash_class.new('trust' => { 'two' => 2 })
158
-
159
- expect(logger_output).to be_blank
160
- end
161
-
162
- it 'cannot disable logging on the base Mash' do
163
- expected_error = Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings
164
-
165
- expect { Hashie::Mash.disable_warnings }.to raise_error(expected_error)
166
- end
167
-
168
- it 'carries over the disable for warnings on grandchild classes' do
169
- child_class = Class.new(Hashie::Mash) do
170
- disable_warnings
171
- end
172
- grandchild_class = Class.new(child_class)
173
-
174
- grandchild_class.new('trust' => { 'two' => 2 })
175
-
176
- expect(logger_output).to be_blank
177
- end
178
-
179
- it 'writes to logger when a key is overridden that is not ignored' do
180
- mash_class = Class.new(Hashie::Mash) do
181
- disable_warnings :merge
182
- end
183
-
184
- mash_class.new('address' => { 'zip' => '90210' })
185
- expect(logger_output).not_to be_blank
186
- end
187
-
188
- it 'does not write to logger when a key is overridden that is ignored' do
189
- mash_class = Class.new(Hashie::Mash) do
190
- disable_warnings :zip
191
- end
192
-
193
- mash_class.new('address' => { 'zip' => '90210' })
194
- expect(logger_output).to be_blank
195
- end
196
-
197
- it 'carries over the ignored warnings list for warnings on grandchild classes' do
198
- child_class = Class.new(Hashie::Mash) do
199
- disable_warnings :zip, :merge
200
- end
201
- grandchild_class = Class.new(child_class)
202
-
203
- grandchild_class.new('address' => { 'zip' => '90210' }, 'merge' => true)
204
-
205
- expect(grandchild_class.disabled_warnings).to eq(%i[zip merge])
206
- expect(logger_output).to be_blank
207
- end
208
-
209
- context 'multiple disable_warnings calls' do
210
- context 'calling disable_warnings multiple times with parameters' do
211
- it 'appends each new parameter to the ignore list' do
212
- child_class = Class.new(Hashie::Mash) do
213
- disable_warnings :zip
214
- disable_warnings :merge
215
- disable_warnings :cycle
216
- end
217
-
218
- expect(child_class.disabled_warnings).to eq(%i[zip merge cycle])
219
- end
220
- end
221
-
222
- context 'calling disable_warnings without keys after calling with keys' do
223
- it 'uses the last call to determine the ignore list' do
224
- child_class = Class.new(Hashie::Mash) do
225
- disable_warnings :zip
226
- disable_warnings
227
- end
228
-
229
- child_class.new('address' => { 'zip' => '90210' }, 'merge' => true, 'cycle' => 'bi')
230
-
231
- expect(child_class.disabled_warnings).to eq([])
232
- expect(logger_output).to be_blank
233
- end
234
- end
235
-
236
- context 'calling disable_parameters with keys after calling without keys' do
237
- it 'only ignores logging for ignored methods' do
238
- child_class = Class.new(Hashie::Mash) do
239
- disable_warnings
240
- disable_warnings :zip
241
- end
242
-
243
- child_class.new('address' => { 'zip' => '90210' }, 'merge' => true)
244
-
245
- expect(logger_output).to match(/#{child_class}#merge/)
246
- expect(logger_output).not_to match(/#{child_class}#zip/)
247
- end
248
- end
249
- end
250
- end
251
-
252
- context 'updating' do
253
- subject do
254
- described_class.new(
255
- first_name: 'Michael',
256
- last_name: 'Bleigh',
257
- details: {
258
- email: 'michael@asf.com',
259
- address: 'Nowhere road'
260
- }
261
- )
262
- end
263
-
264
- describe '#deep_update' do
265
- it 'recursively Hashie::Mash Hashie::Mashes and hashes together' do
266
- subject.deep_update(details: { email: 'michael@intridea.com', city: 'Imagineton' })
267
- expect(subject.first_name).to eq 'Michael'
268
- expect(subject.details.email).to eq 'michael@intridea.com'
269
- expect(subject.details.address).to eq 'Nowhere road'
270
- expect(subject.details.city).to eq 'Imagineton'
271
- end
272
-
273
- it 'converts values only once' do
274
- class ConvertedMash < Hashie::Mash
275
- end
276
-
277
- rhs = ConvertedMash.new(email: 'foo@bar.com')
278
- expect(subject).to receive(:convert_value).exactly(1).times
279
- subject.deep_update(rhs)
280
- end
281
-
282
- it 'makes #update deep by default' do
283
- expect(subject.update(details: { address: 'Fake street' })).to eql(subject)
284
- expect(subject.details.address).to eq 'Fake street'
285
- expect(subject.details.email).to eq 'michael@asf.com'
286
- end
287
-
288
- it 'clones before a #deep_merge' do
289
- duped = subject.deep_merge(details: { address: 'Fake street' })
290
- expect(duped).not_to eql(subject)
291
- expect(duped.details.address).to eq 'Fake street'
292
- expect(subject.details.address).to eq 'Nowhere road'
293
- expect(duped.details.email).to eq 'michael@asf.com'
294
- end
295
-
296
- it 'default #merge is deep' do
297
- duped = subject.merge(details: { email: 'michael@intridea.com' })
298
- expect(duped).not_to eql(subject)
299
- expect(duped.details.email).to eq 'michael@intridea.com'
300
- expect(duped.details.address).to eq 'Nowhere road'
301
- end
302
-
303
- # http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-update
304
- it 'accepts a block' do
305
- duped = subject.merge(details: { address: 'Pasadena CA' }) do |_, oldv, newv|
306
- [oldv, newv].join(', ')
307
- end
308
-
309
- expect(duped.details.address).to eq 'Nowhere road, Pasadena CA'
310
- end
311
-
312
- it 'copies values for non-duplicate keys when a block is supplied' do
313
- m_hash = { details: { address: 'Pasadena CA', state: 'West Thoughtleby' } }
314
- duped = subject.merge(m_hash) { |_, oldv, _| oldv }
315
-
316
- expect(duped.details.address).to eq 'Nowhere road'
317
- expect(duped.details.state).to eq 'West Thoughtleby'
318
- end
319
-
320
- it 'does not raise an exception when default_proc raises an error' do
321
- hash = described_class.new(a: 1) { |_k, _v| raise('Should not be raise I') }
322
- other_has = described_class.new(a: 2, b: 2) { |_k, _v| raise('Should not be raise II') }
323
- expected_hash = described_class.new(a: 2, b: 2)
324
-
325
- res = hash.merge(other_has)
326
- expect(res).to eq(expected_hash)
327
- end
328
- end
329
-
330
- describe 'shallow update' do
331
- it 'shallowly Hashie::Mash Hashie::Mashes and hashes together' do
332
- expect(subject.shallow_update(details: { email: 'michael@intridea.com',
333
- city: 'Imagineton' })).to eql(subject)
334
-
335
- expect(subject.first_name).to eq 'Michael'
336
- expect(subject.details.email).to eq 'michael@intridea.com'
337
- expect(subject.details.address).to be_nil
338
- expect(subject.details.city).to eq 'Imagineton'
339
- end
340
-
341
- it 'clones before a #regular_merge' do
342
- duped = subject.shallow_merge(details: { address: 'Fake street' })
343
- expect(duped).not_to eql(subject)
344
- end
345
-
346
- it 'default #merge is shallow' do
347
- duped = subject.shallow_merge(details: { address: 'Fake street' })
348
- expect(duped.details.address).to eq 'Fake street'
349
- expect(subject.details.address).to eq 'Nowhere road'
350
- expect(duped.details.email).to be_nil
351
- end
352
- end
353
-
354
- describe '#replace' do
355
- before do
356
- subject.replace(
357
- middle_name: 'Cain',
358
- details: { city: 'Imagination' }
359
- )
360
- end
361
-
362
- it 'returns self' do
363
- expect(subject.replace(foo: 'bar').to_hash).to eq('foo' => 'bar')
364
- end
365
-
366
- it 'sets all specified keys to their corresponding values' do
367
- expect(subject.middle_name?).to be_truthy
368
- expect(subject.details?).to be_truthy
369
- expect(subject.middle_name).to eq 'Cain'
370
- expect(subject.details.city?).to be_truthy
371
- expect(subject.details.city).to eq 'Imagination'
372
- end
373
-
374
- it 'leaves only specified keys' do
375
- expect(subject.keys.sort).to eq %w[details middle_name]
376
- expect(subject.first_name?).to be_falsy
377
- expect(subject).not_to respond_to(:first_name)
378
- expect(subject.last_name?).to be_falsy
379
- expect(subject).not_to respond_to(:last_name)
380
- end
381
- end
382
-
383
- describe 'delete' do
384
- it 'deletes with String key' do
385
- subject.delete('details')
386
- expect(subject.details).to be_nil
387
- expect(subject).not_to be_respond_to :details
388
- end
389
-
390
- it 'deletes with Symbol key' do
391
- subject.delete(:details)
392
- expect(subject.details).to be_nil
393
- expect(subject).not_to be_respond_to :details
394
- end
395
- end
396
- end
397
-
398
- it 'converts hash assignments into Hashie::Mashes' do
399
- subject.details = { email: 'randy@asf.com', address: { state: 'TX' } }
400
- expect(subject.details.email).to eq 'randy@asf.com'
401
- expect(subject.details.address.state).to eq 'TX'
402
- end
403
-
404
- it 'does not convert the type of Hashie::Mashes childs to Hashie::Mash' do
405
- class MyMash < Hashie::Mash
406
- end
407
-
408
- record = MyMash.new
409
- record.son = MyMash.new
410
- expect(record.son.class).to eq MyMash
411
- end
412
-
413
- it 'does not change the class of Mashes when converted' do
414
- class SubMash < Hashie::Mash
415
- end
416
-
417
- record = Hashie::Mash.new
418
- son = SubMash.new
419
- record['submash'] = son
420
- expect(record['submash']).to be_kind_of(SubMash)
421
- end
422
-
423
- it 'respects the class when passed a bang method for a non-existent key' do
424
- record = Hashie::Mash.new
425
- expect(record.non_existent!).to be_kind_of(Hashie::Mash)
426
-
427
- class SubMash < Hashie::Mash
428
- end
429
-
430
- son = SubMash.new
431
- expect(son.non_existent!).to be_kind_of(SubMash)
432
- end
433
-
434
- it 'respects the class when passed an under bang method for a non-existent key' do
435
- record = Hashie::Mash.new
436
- expect(record.non_existent_).to be_kind_of(Hashie::Mash)
437
-
438
- class SubMash < Hashie::Mash
439
- end
440
-
441
- son = SubMash.new
442
- expect(son.non_existent_).to be_kind_of(SubMash)
443
- end
444
-
445
- it 'respects the class when converting the value' do
446
- record = Hashie::Mash.new
447
- record.details = Hashie::Mash.new(email: 'randy@asf.com')
448
- expect(record.details).to be_kind_of(Hashie::Mash)
449
- end
450
-
451
- it 'respects another subclass when converting the value' do
452
- record = Hashie::Mash.new
453
-
454
- class SubMash < Hashie::Mash
455
- end
456
-
457
- son = SubMash.new(email: 'foo@bar.com')
458
- record.details = son
459
- expect(record.details).to be_kind_of(SubMash)
460
- end
461
-
462
- describe '#respond_to?' do
463
- subject do
464
- Hashie::Mash.new(abc: 'def')
465
- end
466
-
467
- it 'responds to a normal method' do
468
- expect(subject).to be_respond_to(:key?)
469
- end
470
-
471
- it 'responds to a set key' do
472
- expect(subject).to be_respond_to(:abc)
473
- expect(subject.method(:abc)).to_not be_nil
474
- end
475
-
476
- it 'responds to a set key with a suffix' do
477
- %w[= ? ! _].each do |suffix|
478
- expect(subject).to be_respond_to(:"abc#{suffix}")
479
- end
480
- end
481
-
482
- it 'is able to access the suffixed key as a method' do
483
- %w[= ? ! _].each do |suffix|
484
- expect(subject.method(:"abc#{suffix}")).to_not be_nil
485
- end
486
- end
487
-
488
- it 'responds to an unknown key with a suffix' do
489
- %w[= ? ! _].each do |suffix|
490
- expect(subject).to be_respond_to(:"xyz#{suffix}")
491
- end
492
- end
493
-
494
- it 'is able to access an unknown suffixed key as a method' do
495
- # See https://github.com/intridea/hashie/pull/285 for more information
496
- pending_for(engine: 'ruby', versions: %w[2.2.0 2.2.1 2.2.2])
497
-
498
- %w[= ? ! _].each do |suffix|
499
- expect(subject.method(:"xyz#{suffix}")).to_not be_nil
500
- end
501
- end
502
-
503
- it 'does not respond to an unknown key without a suffix' do
504
- expect(subject).not_to be_respond_to(:xyz)
505
- expect { subject.method(:xyz) }.to raise_error(NameError)
506
- end
507
- end
508
-
509
- context '#initialize' do
510
- it 'converts an existing hash to a Hashie::Mash' do
511
- converted = Hashie::Mash.new(abc: 123, name: 'Bob')
512
- expect(converted.abc).to eq 123
513
- expect(converted.name).to eq 'Bob'
514
- end
515
-
516
- it 'converts hashes recursively into Hashie::Mashes' do
517
- converted = Hashie::Mash.new(a: { b: 1, c: { d: 23 } })
518
- expect(converted.a.is_a?(Hashie::Mash)).to be_truthy
519
- expect(converted.a.b).to eq 1
520
- expect(converted.a.c.d).to eq 23
521
- end
522
-
523
- it 'converts hashes in arrays into Hashie::Mashes' do
524
- converted = Hashie::Mash.new(a: [{ b: 12 }, 23])
525
- expect(converted.a.first.b).to eq 12
526
- expect(converted.a.last).to eq 23
527
- end
528
-
529
- it 'converts an existing Hashie::Mash into a Hashie::Mash' do
530
- initial = Hashie::Mash.new(name: 'randy', address: { state: 'TX' })
531
- copy = Hashie::Mash.new(initial)
532
- expect(initial.name).to eq copy.name
533
- expect(initial.__id__).not_to eq copy.__id__
534
- expect(copy.address.state).to eq 'TX'
535
- copy.address.state = 'MI'
536
- expect(initial.address.state).to eq 'TX'
537
- expect(copy.address.__id__).not_to eq initial.address.__id__
538
- end
539
-
540
- it 'accepts a default block' do
541
- initial = Hashie::Mash.new { |h, i| h[i] = [] }
542
- expect(initial.default_proc).not_to be_nil
543
- expect(initial.default).to be_nil
544
- expect(initial.test).to eq []
545
- expect(initial.test?).to be_truthy
546
- end
547
-
548
- it 'allows propagation of a default block' do
549
- h = Hashie::Mash.new { |mash, key| mash[key] = mash.class.new(&mash.default_proc) }
550
- expect { h[:x][:y][:z] = :xyz }.not_to raise_error
551
- expect(h.x.y.z).to eq(:xyz)
552
- expect(h[:x][:y][:z]).to eq(:xyz)
553
- end
554
-
555
- it 'allows assignment of an empty array in a default block' do
556
- initial = Hashie::Mash.new { |h, k| h[k] = [] }
557
- initial.hello << 100
558
- expect(initial.hello).to eq [100]
559
- initial['hi'] << 100
560
- expect(initial['hi']).to eq [100]
561
- end
562
-
563
- it 'allows assignment of a non-empty array in a default block' do
564
- initial = Hashie::Mash.new { |h, k| h[k] = [100] }
565
- initial.hello << 200
566
- expect(initial.hello).to eq [100, 200]
567
- initial['hi'] << 200
568
- expect(initial['hi']).to eq [100, 200]
569
- end
570
-
571
- it 'allows assignment of an empty hash in a default block' do
572
- initial = Hashie::Mash.new { |h, k| h[k] = {} }
573
- initial.hello[:a] = 100
574
- expect(initial.hello).to eq Hashie::Mash.new(a: 100)
575
- initial[:hi][:a] = 100
576
- expect(initial[:hi]).to eq Hashie::Mash.new(a: 100)
577
- end
578
-
579
- it 'allows assignment of a non-empty hash in a default block' do
580
- initial = Hashie::Mash.new { |h, k| h[k] = { a: 100 } }
581
- initial.hello[:b] = 200
582
- expect(initial.hello).to eq Hashie::Mash.new(a: 100, b: 200)
583
- initial[:hi][:b] = 200
584
- expect(initial[:hi]).to eq Hashie::Mash.new(a: 100, b: 200)
585
- end
586
-
587
- it 'converts Hashie::Mashes within Arrays back to Hashes' do
588
- initial_hash = { 'a' => [{ 'b' => 12, 'c' => ['d' => 50, 'e' => 51] }, 23] }
589
- converted = Hashie::Mash.new(initial_hash)
590
- expect(converted.to_hash['a'].first.is_a?(Hashie::Mash)).to be_falsy
591
- expect(converted.to_hash['a'].first.is_a?(Hash)).to be_truthy
592
- expect(converted.to_hash['a'].first['c'].first.is_a?(Hashie::Mash)).to be_falsy
593
- end
594
- end
595
-
596
- describe '#fetch' do
597
- let(:hash) { { one: 1, other: false } }
598
- let(:mash) { Hashie::Mash.new(hash) }
599
-
600
- context 'when key exists' do
601
- it 'returns the value' do
602
- expect(mash.fetch(:one)).to eql(1)
603
- end
604
-
605
- it 'returns the value even if the value is falsy' do
606
- expect(mash.fetch(:other)).to eql(false)
607
- end
608
-
609
- context 'when key has other than original but acceptable type' do
610
- it 'returns the value' do
611
- expect(mash.fetch('one')).to eql(1)
612
- end
613
- end
614
- end
615
-
616
- context 'when key does not exist' do
617
- it 'raises KeyError' do
618
- error = RUBY_VERSION =~ /1.8/ ? IndexError : KeyError
619
- expect { mash.fetch(:two) }.to raise_error(error)
620
- end
621
-
622
- context 'with default value given' do
623
- it 'returns default value' do
624
- expect(mash.fetch(:two, 8)).to eql(8)
625
- end
626
-
627
- it 'returns default value even if it is falsy' do
628
- expect(mash.fetch(:two, false)).to eql(false)
629
- end
630
- end
631
-
632
- context 'with block given' do
633
- it 'returns default value' do
634
- expect(mash.fetch(:two) do
635
- 'block default value'
636
- end).to eql('block default value')
637
- end
638
- end
639
- end
640
- end
641
-
642
- describe '#to_hash' do
643
- let(:hash) { { 'outer' => { 'inner' => 42 }, 'testing' => [1, 2, 3] } }
644
- let(:mash) { Hashie::Mash.new(hash) }
645
-
646
- it 'returns a standard Hash' do
647
- expect(mash.to_hash).to be_a(::Hash)
648
- end
649
-
650
- it 'includes all keys' do
651
- expect(mash.to_hash.keys).to eql(%w[outer testing])
652
- end
653
-
654
- it 'converts keys to symbols when symbolize_keys option is true' do
655
- expect(mash.to_hash(symbolize_keys: true).keys).to include(:outer)
656
- expect(mash.to_hash(symbolize_keys: true).keys).not_to include('outer')
657
- end
658
-
659
- it 'leaves keys as strings when symbolize_keys option is false' do
660
- expect(mash.to_hash(symbolize_keys: false).keys).to include('outer')
661
- expect(mash.to_hash(symbolize_keys: false).keys).not_to include(:outer)
662
- end
663
-
664
- it 'symbolizes keys recursively' do
665
- expect(mash.to_hash(symbolize_keys: true)[:outer].keys).to include(:inner)
666
- expect(mash.to_hash(symbolize_keys: true)[:outer].keys).not_to include('inner')
667
- end
668
- end
669
-
670
- describe '#stringify_keys' do
671
- it 'turns all keys into strings recursively' do
672
- hash = Hashie::Mash[:a => 'hey', 123 => { 345 => 'hey' }]
673
- hash.stringify_keys!
674
- expect(hash).to eq Hashie::Hash['a' => 'hey', '123' => { '345' => 'hey' }]
675
- end
676
- end
677
-
678
- describe '#values_at' do
679
- let(:hash) { { 'key_one' => 1, :key_two => 2 } }
680
- let(:mash) { Hashie::Mash.new(hash) }
681
-
682
- context 'when the original type is given' do
683
- it 'returns the values' do
684
- expect(mash.values_at('key_one', :key_two)).to eq([1, 2])
685
- end
686
- end
687
-
688
- context 'when a different, but acceptable type is given' do
689
- it 'returns the values' do
690
- expect(mash.values_at(:key_one, 'key_two')).to eq([1, 2])
691
- end
692
- end
693
-
694
- context 'when a key is given that is not in the Mash' do
695
- it 'returns nil for that value' do
696
- expect(mash.values_at('key_one', :key_three)).to eq([1, nil])
697
- end
698
- end
699
- end
700
-
701
- describe '.load(filename, options = {})' do
702
- let(:config) do
703
- {
704
- 'production' => {
705
- 'foo' => 'production_foo'
706
- }
707
- }
708
- end
709
- let(:path) { 'database.yml' }
710
- let(:parser) { double(:parser) }
711
-
712
- subject { described_class.load(path, parser: parser) }
713
-
714
- before do |ex|
715
- unless ex.metadata == :test_cache
716
- described_class.instance_variable_set('@_mashes', nil) # clean the cached mashes
717
- end
718
- end
719
-
720
- context 'if the file exists' do
721
- before do
722
- expect(File).to receive(:file?).with(path).and_return(true)
723
- expect(parser).to receive(:perform).with(path, {}).and_return(config)
724
- end
725
-
726
- it { is_expected.to be_a(Hashie::Mash) }
727
-
728
- it 'return a Mash from a file' do
729
- expect(subject.production).not_to be_nil
730
- expect(subject.production.keys).to eq config['production'].keys
731
- expect(subject.production.foo).to eq config['production']['foo']
732
- end
733
-
734
- it 'freeze the attribtues' do
735
- expect { subject.production = {} }.to raise_exception(RuntimeError, /can't modify frozen/)
736
- end
737
- end
738
-
739
- context 'if the fils does not exists' do
740
- before do
741
- expect(File).to receive(:file?).with(path).and_return(false)
742
- end
743
-
744
- it 'raise an ArgumentError' do
745
- expect { subject }.to raise_exception(ArgumentError)
746
- end
747
- end
748
-
749
- context 'if the file is passed as Pathname' do
750
- require 'pathname'
751
- let(:path) { Pathname.new('database.yml') }
752
-
753
- before do
754
- expect(File).to receive(:file?).with(path).and_return(true)
755
- expect(parser).to receive(:perform).with(path, {}).and_return(config)
756
- end
757
-
758
- it 'return a Mash from a file' do
759
- expect(subject.production.foo).to eq config['production']['foo']
760
- end
761
- end
762
-
763
- describe 'results are cached' do
764
- let(:parser) { double(:parser) }
765
-
766
- subject { described_class.load(path, parser: parser) }
767
-
768
- before do
769
- expect(File).to receive(:file?).with(path).and_return(true)
770
- expect(File).to receive(:file?).with("#{path}+1").and_return(true)
771
- expect(parser).to receive(:perform).once.with(path, {}).and_return(config)
772
- expect(parser).to receive(:perform).once.with("#{path}+1", {}).and_return(config)
773
- end
774
-
775
- it 'cache the loaded yml file', :test_cache do
776
- 2.times do
777
- expect(subject).to be_a(described_class)
778
- expect(described_class.load("#{path}+1", parser: parser)).to be_a(described_class)
779
- end
780
-
781
- expect(subject.object_id).to eq subject.object_id
782
- end
783
- end
784
-
785
- context 'when the file has aliases in it' do
786
- it 'can use the aliases and does not raise an error' do
787
- mash = Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml')
788
- expect(mash.company_a.accounts.admin.password).to eq('secret')
789
- end
790
- it 'can override the value of aliases' do
791
- expect do
792
- Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml', aliases: false)
793
- end.to raise_error Psych::BadAlias, /base_accounts/
794
- end
795
- end
796
-
797
- context 'when the file has symbols' do
798
- it 'can override the value of permitted_classes' do
799
- mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml', permitted_classes: [Symbol])
800
- expect(mash.user_icon.width).to eq(200)
801
- end
802
- it 'uses defaults for permitted_classes' do
803
- expect do
804
- Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml')
805
- end.to raise_error Psych::DisallowedClass, /Symbol/
806
- end
807
- it 'can override the value of permitted_symbols' do
808
- mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
809
- permitted_classes: [Symbol],
810
- permitted_symbols: %i[
811
- user_icon
812
- width
813
- height
814
- ])
815
- expect(mash.user_icon.width).to eq(200)
816
- end
817
- it 'raises an error on insufficient permitted_symbols' do
818
- expect do
819
- Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
820
- permitted_classes: [Symbol],
821
- permitted_symbols: %i[
822
- user_icon
823
- width
824
- ])
825
- end.to raise_error Psych::DisallowedClass, /Symbol/
826
- end
827
- end
828
- end
829
-
830
- describe '#to_module(mash_method_name)' do
831
- let(:mash) { described_class.new }
832
- subject { Class.new.extend mash.to_module }
833
-
834
- it 'defines a settings method on the klass class that extends the module' do
835
- expect(subject).to respond_to(:settings)
836
- expect(subject.settings).to eq mash
837
- end
838
-
839
- context 'when a settings_method_name is set' do
840
- let(:mash_method_name) { 'config' }
841
-
842
- subject { Class.new.extend mash.to_module(mash_method_name) }
843
-
844
- it 'defines a settings method on the klass class that extends the module' do
845
- expect(subject).to respond_to(mash_method_name.to_sym)
846
- expect(subject.send(mash_method_name.to_sym)).to eq mash
847
- end
848
- end
849
- end
850
-
851
- describe '#extractable_options?' do
852
- require 'active_support'
853
-
854
- subject { described_class.new(name: 'foo') }
855
- let(:args) { [101, 'bar', subject] }
856
-
857
- it 'can be extracted from an array' do
858
- expect(args.extract_options!).to eq subject
859
- expect(args).to eq [101, 'bar']
860
- end
861
- end
862
-
863
- describe '#reverse_merge' do
864
- subject { described_class.new(a: 1, b: 2) }
865
-
866
- it 'unifies strings and symbols' do
867
- expect(subject.reverse_merge(a: 2).length).to eq 2
868
- expect(subject.reverse_merge('a' => 2).length).to eq 2
869
- end
870
-
871
- it 'does not overwrite values' do
872
- expect(subject.reverse_merge(a: 5).a).to eq subject.a
873
- end
874
-
875
- context 'when using with subclass' do
876
- let(:subclass) { Class.new(Hashie::Mash) }
877
- subject { subclass.new(a: 1) }
878
-
879
- it 'creates an instance of subclass' do
880
- expect(subject.reverse_merge(a: 5)).to be_kind_of(subclass)
881
- end
882
- end
883
- end
884
-
885
- describe '#compact' do
886
- subject(:mash) { described_class.new(a: 1, b: nil) }
887
-
888
- it 'returns a Hashie::Mash' do
889
- expect(mash.compact).to be_kind_of(described_class)
890
- end
891
-
892
- it 'removes keys with nil values' do
893
- expect(mash.compact).to eq('a' => 1)
894
- end
895
-
896
- context 'when using with subclass' do
897
- let(:subclass) { Class.new(Hashie::Mash) }
898
- subject(:sub_mash) { subclass.new(a: 1, b: nil) }
899
-
900
- it 'creates an instance of subclass' do
901
- expect(sub_mash.compact).to be_kind_of(subclass)
902
- end
903
- end
904
- end
905
-
906
- describe '#invert' do
907
- subject(:mash) { described_class.new(a: 'apple', b: 4) }
908
-
909
- it 'returns a Hashie::Mash' do
910
- expect(mash.invert).to be_kind_of(described_class)
911
- end
912
-
913
- it 'returns a mash with the keys and values inverted' do
914
- expect(mash.invert).to eq('apple' => 'a', '4' => 'b')
915
- end
916
-
917
- context 'when using with subclass' do
918
- let(:subclass) { Class.new(Hashie::Mash) }
919
- subject(:sub_mash) { subclass.new(a: 1, b: nil) }
920
-
921
- it 'creates an instance of subclass' do
922
- expect(sub_mash.invert).to be_kind_of(subclass)
923
- end
924
- end
925
- end
926
-
927
- describe '#reject' do
928
- subject(:mash) { described_class.new(a: 1, b: nil) }
929
-
930
- it 'returns a Hashie::Mash' do
931
- expect(mash.reject { |_k, v| v.nil? }).to be_kind_of(described_class)
932
- end
933
-
934
- it 'rejects keys for which the block returns true' do
935
- expect(mash.reject { |_k, v| v.nil? }).to eq('a' => 1)
936
- end
937
-
938
- context 'when using with subclass' do
939
- let(:subclass) { Class.new(Hashie::Mash) }
940
- subject(:sub_mash) { subclass.new(a: 1, b: nil) }
941
-
942
- it 'creates an instance of subclass' do
943
- expect(sub_mash.reject { |_k, v| v.nil? }).to be_kind_of(subclass)
944
- end
945
- end
946
- end
947
-
948
- describe '#select' do
949
- subject(:mash) { described_class.new(a: 'apple', b: 4) }
950
-
951
- it 'returns a Hashie::Mash' do
952
- expect(mash.select { |_k, v| v.is_a? String }).to be_kind_of(described_class)
953
- end
954
-
955
- it 'selects keys for which the block returns true' do
956
- expect(mash.select { |_k, v| v.is_a? String }).to eq('a' => 'apple')
957
- end
958
-
959
- context 'when using with subclass' do
960
- let(:subclass) { Class.new(Hashie::Mash) }
961
- subject(:sub_mash) { subclass.new(a: 1, b: nil) }
962
-
963
- it 'creates an instance of subclass' do
964
- expect(sub_mash.select { |_k, v| v.is_a? String }).to be_kind_of(subclass)
965
- end
966
- end
967
- end
968
-
969
- describe '.quiet' do
970
- it 'returns a subclass of the calling class' do
971
- expect(Hashie::Mash.quiet.new).to be_a(Hashie::Mash)
972
- end
973
-
974
- it 'memoizes and returns classes' do
975
- call_one = Hashie::Mash.quiet
976
- call_two = Hashie::Mash.quiet
977
- expect(Hashie::Mash.instance_variable_get('@memoized_classes').count).to eq(1)
978
- expect(call_one).to eq(call_two)
979
- end
980
- end
981
-
982
- with_minimum_ruby('2.3.0') do
983
- describe '#dig' do
984
- subject { described_class.new(a: { b: 1 }) }
985
- it 'accepts both string and symbol as key' do
986
- expect(subject.dig(:a, :b)).to eq(1)
987
- expect(subject.dig('a', 'b')).to eq(1)
988
- end
989
-
990
- context 'with numeric key' do
991
- subject { described_class.new('1' => { b: 1 }) }
992
- it 'accepts a numeric value as key' do
993
- expect(subject.dig(1, :b)).to eq(1)
994
- expect(subject.dig('1', :b)).to eq(1)
995
- end
996
- end
997
- end
998
- end
999
-
1000
- with_minimum_ruby('2.4.0') do
1001
- describe '#transform_values' do
1002
- subject(:mash) { described_class.new(a: 1) }
1003
-
1004
- it 'returns a Hashie::Mash' do
1005
- expect(mash.transform_values(&:to_s)).to be_kind_of(described_class)
1006
- end
1007
-
1008
- it 'transforms the value' do
1009
- expect(mash.transform_values(&:to_s).a).to eql('1')
1010
- end
1011
-
1012
- context 'when using with subclass' do
1013
- let(:subclass) { Class.new(Hashie::Mash) }
1014
- subject(:sub_mash) { subclass.new(a: 1).transform_values { |a| a + 2 } }
1015
-
1016
- it 'creates an instance of subclass' do
1017
- expect(sub_mash).to be_kind_of(subclass)
1018
- end
1019
- end
1020
- end
1021
- end
1022
-
1023
- with_minimum_ruby('2.5.0') do
1024
- describe '#slice' do
1025
- subject(:mash) { described_class.new(a: 1, b: 2) }
1026
-
1027
- it 'returns a Hashie::Mash' do
1028
- expect(mash.slice(:a)).to be_kind_of(described_class)
1029
- end
1030
-
1031
- it 'returns a Mash with only the keys passed' do
1032
- expect(mash.slice(:a).to_hash).to eq('a' => 1)
1033
- end
1034
-
1035
- context 'when using with subclass' do
1036
- let(:subclass) { Class.new(Hashie::Mash) }
1037
- subject(:sub_mash) { subclass.new(a: 1, b: 2) }
1038
-
1039
- it 'creates an instance of subclass' do
1040
- expect(sub_mash.slice(:a)).to be_kind_of(subclass)
1041
- end
1042
- end
1043
- end
1044
-
1045
- describe '#transform_keys' do
1046
- subject(:mash) { described_class.new(a: 1, b: 2) }
1047
-
1048
- it 'returns a Hashie::Mash' do
1049
- expect(mash.transform_keys { |k| k + k }).to be_kind_of(described_class)
1050
- end
1051
-
1052
- it 'returns a Mash with transformed keys' do
1053
- expect(mash.transform_keys { |k| k + k }).to eq('aa' => 1, 'bb' => 2)
1054
- end
1055
-
1056
- context 'when using with subclass' do
1057
- let(:subclass) { Class.new(Hashie::Mash) }
1058
- subject(:sub_mash) { subclass.new(a: 1, b: 2) }
1059
-
1060
- it 'creates an instance of subclass' do
1061
- expect(sub_mash.transform_keys { |k| k + k }).to be_kind_of(subclass)
1062
- end
1063
- end
1064
- end
1065
- end
1066
-
1067
- with_minimum_ruby('2.6.0') do
1068
- context 'ruby 2.6 merging' do
1069
- subject(:mash) { Hashie::Mash.new(model: 'Honda') }
1070
- it 'merges multiple hashes and mashes passeed to #merge' do
1071
- first_hash = { model: 'Ford' }
1072
- second_hash = { model: 'DeLorean' }
1073
- expect(mash.merge(first_hash, second_hash)).to eq('model' => 'DeLorean')
1074
- end
1075
- end
1076
- end
1077
- end