hashie 4.0.0 → 4.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.
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