hashie 3.5.7 → 5.0.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 (85) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +281 -195
  3. data/CONTRIBUTING.md +13 -6
  4. data/LICENSE +1 -1
  5. data/README.md +320 -60
  6. data/Rakefile +2 -2
  7. data/UPGRADING.md +121 -7
  8. data/hashie.gemspec +13 -7
  9. data/lib/hashie/clash.rb +12 -1
  10. data/lib/hashie/dash.rb +56 -35
  11. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  12. data/lib/hashie/extensions/coercion.rb +26 -19
  13. data/lib/hashie/extensions/dash/indifferent_access.rb +29 -1
  14. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  15. data/lib/hashie/extensions/dash/property_translation.rb +59 -28
  16. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  17. data/lib/hashie/extensions/deep_find.rb +14 -5
  18. data/lib/hashie/extensions/deep_locate.rb +22 -8
  19. data/lib/hashie/extensions/deep_merge.rb +26 -10
  20. data/lib/hashie/extensions/ignore_undeclared.rb +4 -5
  21. data/lib/hashie/extensions/indifferent_access.rb +43 -10
  22. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  23. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  24. data/lib/hashie/extensions/mash/keep_original_keys.rb +4 -5
  25. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  26. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  27. data/lib/hashie/extensions/mash/symbolize_keys.rb +6 -6
  28. data/lib/hashie/extensions/method_access.rb +47 -14
  29. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -4
  30. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  31. data/lib/hashie/extensions/strict_key_access.rb +16 -13
  32. data/lib/hashie/extensions/stringify_keys.rb +1 -1
  33. data/lib/hashie/extensions/symbolize_keys.rb +13 -2
  34. data/lib/hashie/hash.rb +18 -11
  35. data/lib/hashie/mash.rb +147 -81
  36. data/lib/hashie/railtie.rb +7 -0
  37. data/lib/hashie/rash.rb +6 -6
  38. data/lib/hashie/utils.rb +28 -0
  39. data/lib/hashie/version.rb +1 -1
  40. data/lib/hashie.rb +22 -19
  41. metadata +23 -131
  42. data/spec/hashie/array_spec.rb +0 -29
  43. data/spec/hashie/clash_spec.rb +0 -70
  44. data/spec/hashie/dash_spec.rb +0 -573
  45. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  46. data/spec/hashie/extensions/coercion_spec.rb +0 -631
  47. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  48. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  49. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  50. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  51. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  52. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  53. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  54. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -282
  55. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  56. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  57. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  58. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  59. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  60. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  61. data/spec/hashie/extensions/method_access_spec.rb +0 -188
  62. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  63. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  64. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  65. data/spec/hashie/hash_spec.rb +0 -84
  66. data/spec/hashie/mash_spec.rb +0 -763
  67. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  68. data/spec/hashie/rash_spec.rb +0 -83
  69. data/spec/hashie/trash_spec.rb +0 -268
  70. data/spec/hashie/utils_spec.rb +0 -25
  71. data/spec/hashie/version_spec.rb +0 -7
  72. data/spec/hashie_spec.rb +0 -13
  73. data/spec/integration/omniauth/app.rb +0 -11
  74. data/spec/integration/omniauth/integration_spec.rb +0 -38
  75. data/spec/integration/omniauth-oauth2/app.rb +0 -53
  76. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  77. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  78. data/spec/integration/rails/app.rb +0 -48
  79. data/spec/integration/rails/integration_spec.rb +0 -26
  80. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  81. data/spec/spec_helper.rb +0 -23
  82. data/spec/support/integration_specs.rb +0 -36
  83. data/spec/support/logger.rb +0 -24
  84. data/spec/support/module_context.rb +0 -11
  85. data/spec/support/ruby_version_check.rb +0 -6
@@ -1,763 +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
-
158
- mash_class.new('trust' => { 'two' => 2 })
159
-
160
- expect(logger_output).to be_blank
161
- end
162
-
163
- it 'cannot disable logging on the base Mash' do
164
- expect { Hashie::Mash.disable_warnings }.to raise_error(Hashie::Mash::CannotDisableMashWarnings)
165
- end
166
-
167
- it 'carries over the disable for warnings on grandchild classes' do
168
- child_class = Class.new(Hashie::Mash) do
169
- disable_warnings
170
- end
171
- grandchild_class = Class.new(child_class)
172
-
173
- grandchild_class.new('trust' => { 'two' => 2 })
174
-
175
- expect(logger_output).to be_blank
176
- end
177
- end
178
-
179
- context 'updating' do
180
- subject do
181
- described_class.new(
182
- first_name: 'Michael',
183
- last_name: 'Bleigh',
184
- details: {
185
- email: 'michael@asf.com',
186
- address: 'Nowhere road'
187
- })
188
- end
189
-
190
- describe '#deep_update' do
191
- it 'recursively Hashie::Mash Hashie::Mashes and hashes together' do
192
- subject.deep_update(details: { email: 'michael@intridea.com', city: 'Imagineton' })
193
- expect(subject.first_name).to eq 'Michael'
194
- expect(subject.details.email).to eq 'michael@intridea.com'
195
- expect(subject.details.address).to eq 'Nowhere road'
196
- expect(subject.details.city).to eq 'Imagineton'
197
- end
198
-
199
- it 'converts values only once' do
200
- class ConvertedMash < Hashie::Mash
201
- end
202
-
203
- rhs = ConvertedMash.new(email: 'foo@bar.com')
204
- expect(subject).to receive(:convert_value).exactly(1).times
205
- subject.deep_update(rhs)
206
- end
207
-
208
- it 'makes #update deep by default' do
209
- expect(subject.update(details: { address: 'Fake street' })).to eql(subject)
210
- expect(subject.details.address).to eq 'Fake street'
211
- expect(subject.details.email).to eq 'michael@asf.com'
212
- end
213
-
214
- it 'clones before a #deep_merge' do
215
- duped = subject.deep_merge(details: { address: 'Fake street' })
216
- expect(duped).not_to eql(subject)
217
- expect(duped.details.address).to eq 'Fake street'
218
- expect(subject.details.address).to eq 'Nowhere road'
219
- expect(duped.details.email).to eq 'michael@asf.com'
220
- end
221
-
222
- it 'default #merge is deep' do
223
- duped = subject.merge(details: { email: 'michael@intridea.com' })
224
- expect(duped).not_to eql(subject)
225
- expect(duped.details.email).to eq 'michael@intridea.com'
226
- expect(duped.details.address).to eq 'Nowhere road'
227
- end
228
-
229
- # http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-update
230
- it 'accepts a block' do
231
- duped = subject.merge(details: { address: 'Pasadena CA' }) { |_, oldv, newv| [oldv, newv].join(', ') }
232
- expect(duped.details.address).to eq 'Nowhere road, Pasadena CA'
233
- end
234
-
235
- it 'copies values for non-duplicate keys when a block is supplied' do
236
- duped = subject.merge(details: { address: 'Pasadena CA', state: 'West Thoughtleby' }) { |_, oldv, _| oldv }
237
- expect(duped.details.address).to eq 'Nowhere road'
238
- expect(duped.details.state).to eq 'West Thoughtleby'
239
- end
240
- end
241
-
242
- describe 'shallow update' do
243
- it 'shallowly Hashie::Mash Hashie::Mashes and hashes together' do
244
- expect(subject.shallow_update(details: { email: 'michael@intridea.com',
245
- city: 'Imagineton' })).to eql(subject)
246
-
247
- expect(subject.first_name).to eq 'Michael'
248
- expect(subject.details.email).to eq 'michael@intridea.com'
249
- expect(subject.details.address).to be_nil
250
- expect(subject.details.city).to eq 'Imagineton'
251
- end
252
-
253
- it 'clones before a #regular_merge' do
254
- duped = subject.shallow_merge(details: { address: 'Fake street' })
255
- expect(duped).not_to eql(subject)
256
- end
257
-
258
- it 'default #merge is shallow' do
259
- duped = subject.shallow_merge(details: { address: 'Fake street' })
260
- expect(duped.details.address).to eq 'Fake street'
261
- expect(subject.details.address).to eq 'Nowhere road'
262
- expect(duped.details.email).to be_nil
263
- end
264
- end
265
-
266
- describe '#replace' do
267
- before do
268
- subject.replace(
269
- middle_name: 'Cain',
270
- details: { city: 'Imagination' }
271
- )
272
- end
273
-
274
- it 'returns self' do
275
- expect(subject.replace(foo: 'bar').to_hash).to eq('foo' => 'bar')
276
- end
277
-
278
- it 'sets all specified keys to their corresponding values' do
279
- expect(subject.middle_name?).to be_truthy
280
- expect(subject.details?).to be_truthy
281
- expect(subject.middle_name).to eq 'Cain'
282
- expect(subject.details.city?).to be_truthy
283
- expect(subject.details.city).to eq 'Imagination'
284
- end
285
-
286
- it 'leaves only specified keys' do
287
- expect(subject.keys.sort).to eq %w(details middle_name)
288
- expect(subject.first_name?).to be_falsy
289
- expect(subject).not_to respond_to(:first_name)
290
- expect(subject.last_name?).to be_falsy
291
- expect(subject).not_to respond_to(:last_name)
292
- end
293
- end
294
-
295
- describe 'delete' do
296
- it 'deletes with String key' do
297
- subject.delete('details')
298
- expect(subject.details).to be_nil
299
- expect(subject).not_to be_respond_to :details
300
- end
301
-
302
- it 'deletes with Symbol key' do
303
- subject.delete(:details)
304
- expect(subject.details).to be_nil
305
- expect(subject).not_to be_respond_to :details
306
- end
307
- end
308
- end
309
-
310
- it 'converts hash assignments into Hashie::Mashes' do
311
- subject.details = { email: 'randy@asf.com', address: { state: 'TX' } }
312
- expect(subject.details.email).to eq 'randy@asf.com'
313
- expect(subject.details.address.state).to eq 'TX'
314
- end
315
-
316
- it 'does not convert the type of Hashie::Mashes childs to Hashie::Mash' do
317
- class MyMash < Hashie::Mash
318
- end
319
-
320
- record = MyMash.new
321
- record.son = MyMash.new
322
- expect(record.son.class).to eq MyMash
323
- end
324
-
325
- it 'does not change the class of Mashes when converted' do
326
- class SubMash < Hashie::Mash
327
- end
328
-
329
- record = Hashie::Mash.new
330
- son = SubMash.new
331
- record['submash'] = son
332
- expect(record['submash']).to be_kind_of(SubMash)
333
- end
334
-
335
- it 'respects the class when passed a bang method for a non-existent key' do
336
- record = Hashie::Mash.new
337
- expect(record.non_existent!).to be_kind_of(Hashie::Mash)
338
-
339
- class SubMash < Hashie::Mash
340
- end
341
-
342
- son = SubMash.new
343
- expect(son.non_existent!).to be_kind_of(SubMash)
344
- end
345
-
346
- it 'respects the class when passed an under bang method for a non-existent key' do
347
- record = Hashie::Mash.new
348
- expect(record.non_existent_).to be_kind_of(Hashie::Mash)
349
-
350
- class SubMash < Hashie::Mash
351
- end
352
-
353
- son = SubMash.new
354
- expect(son.non_existent_).to be_kind_of(SubMash)
355
- end
356
-
357
- it 'respects the class when converting the value' do
358
- record = Hashie::Mash.new
359
- record.details = Hashie::Mash.new(email: 'randy@asf.com')
360
- expect(record.details).to be_kind_of(Hashie::Mash)
361
- end
362
-
363
- it 'respects another subclass when converting the value' do
364
- record = Hashie::Mash.new
365
-
366
- class SubMash < Hashie::Mash
367
- end
368
-
369
- son = SubMash.new(email: 'foo@bar.com')
370
- record.details = son
371
- expect(record.details).to be_kind_of(SubMash)
372
- end
373
-
374
- describe '#respond_to?' do
375
- subject do
376
- Hashie::Mash.new(abc: 'def')
377
- end
378
-
379
- it 'responds to a normal method' do
380
- expect(subject).to be_respond_to(:key?)
381
- end
382
-
383
- it 'responds to a set key' do
384
- expect(subject).to be_respond_to(:abc)
385
- expect(subject.method(:abc)).to_not be_nil
386
- end
387
-
388
- it 'responds to a set key with a suffix' do
389
- %w(= ? ! _).each do |suffix|
390
- expect(subject).to be_respond_to(:"abc#{suffix}")
391
- end
392
- end
393
-
394
- it 'is able to access the suffixed key as a method' do
395
- %w(= ? ! _).each do |suffix|
396
- expect(subject.method(:"abc#{suffix}")).to_not be_nil
397
- end
398
- end
399
-
400
- it 'responds to an unknown key with a suffix' do
401
- %w(= ? ! _).each do |suffix|
402
- expect(subject).to be_respond_to(:"xyz#{suffix}")
403
- end
404
- end
405
-
406
- it 'is able to access an unknown suffixed key as a method' do
407
- # See https://github.com/intridea/hashie/pull/285 for more information
408
- pending_for(engine: 'ruby', versions: %w(2.2.0 2.2.1 2.2.2))
409
-
410
- %w(= ? ! _).each do |suffix|
411
- expect(subject.method(:"xyz#{suffix}")).to_not be_nil
412
- end
413
- end
414
-
415
- it 'does not respond to an unknown key without a suffix' do
416
- expect(subject).not_to be_respond_to(:xyz)
417
- expect { subject.method(:xyz) }.to raise_error(NameError)
418
- end
419
- end
420
-
421
- context '#initialize' do
422
- it 'converts an existing hash to a Hashie::Mash' do
423
- converted = Hashie::Mash.new(abc: 123, name: 'Bob')
424
- expect(converted.abc).to eq 123
425
- expect(converted.name).to eq 'Bob'
426
- end
427
-
428
- it 'converts hashes recursively into Hashie::Mashes' do
429
- converted = Hashie::Mash.new(a: { b: 1, c: { d: 23 } })
430
- expect(converted.a.is_a?(Hashie::Mash)).to be_truthy
431
- expect(converted.a.b).to eq 1
432
- expect(converted.a.c.d).to eq 23
433
- end
434
-
435
- it 'converts hashes in arrays into Hashie::Mashes' do
436
- converted = Hashie::Mash.new(a: [{ b: 12 }, 23])
437
- expect(converted.a.first.b).to eq 12
438
- expect(converted.a.last).to eq 23
439
- end
440
-
441
- it 'converts an existing Hashie::Mash into a Hashie::Mash' do
442
- initial = Hashie::Mash.new(name: 'randy', address: { state: 'TX' })
443
- copy = Hashie::Mash.new(initial)
444
- expect(initial.name).to eq copy.name
445
- expect(initial.__id__).not_to eq copy.__id__
446
- expect(copy.address.state).to eq 'TX'
447
- copy.address.state = 'MI'
448
- expect(initial.address.state).to eq 'TX'
449
- expect(copy.address.__id__).not_to eq initial.address.__id__
450
- end
451
-
452
- it 'accepts a default block' do
453
- initial = Hashie::Mash.new { |h, i| h[i] = [] }
454
- expect(initial.default_proc).not_to be_nil
455
- expect(initial.default).to be_nil
456
- expect(initial.test).to eq []
457
- expect(initial.test?).to be_truthy
458
- end
459
-
460
- it 'allows assignment of an empty array in a default block' do
461
- initial = Hashie::Mash.new { |h, k| h[k] = [] }
462
- initial.hello << 100
463
- expect(initial.hello).to eq [100]
464
- initial['hi'] << 100
465
- expect(initial['hi']).to eq [100]
466
- end
467
-
468
- it 'allows assignment of a non-empty array in a default block' do
469
- initial = Hashie::Mash.new { |h, k| h[k] = [100] }
470
- initial.hello << 200
471
- expect(initial.hello).to eq [100, 200]
472
- initial['hi'] << 200
473
- expect(initial['hi']).to eq [100, 200]
474
- end
475
-
476
- it 'allows assignment of an empty hash in a default block' do
477
- initial = Hashie::Mash.new { |h, k| h[k] = {} }
478
- initial.hello[:a] = 100
479
- expect(initial.hello).to eq Hashie::Mash.new(a: 100)
480
- initial[:hi][:a] = 100
481
- expect(initial[:hi]).to eq Hashie::Mash.new(a: 100)
482
- end
483
-
484
- it 'allows assignment of a non-empty hash in a default block' do
485
- initial = Hashie::Mash.new { |h, k| h[k] = { a: 100 } }
486
- initial.hello[:b] = 200
487
- expect(initial.hello).to eq Hashie::Mash.new(a: 100, b: 200)
488
- initial[:hi][:b] = 200
489
- expect(initial[:hi]).to eq Hashie::Mash.new(a: 100, b: 200)
490
- end
491
-
492
- it 'converts Hashie::Mashes within Arrays back to Hashes' do
493
- initial_hash = { 'a' => [{ 'b' => 12, 'c' => ['d' => 50, 'e' => 51] }, 23] }
494
- converted = Hashie::Mash.new(initial_hash)
495
- expect(converted.to_hash['a'].first.is_a?(Hashie::Mash)).to be_falsy
496
- expect(converted.to_hash['a'].first.is_a?(Hash)).to be_truthy
497
- expect(converted.to_hash['a'].first['c'].first.is_a?(Hashie::Mash)).to be_falsy
498
- end
499
- end
500
-
501
- describe '#fetch' do
502
- let(:hash) { { one: 1, other: false } }
503
- let(:mash) { Hashie::Mash.new(hash) }
504
-
505
- context 'when key exists' do
506
- it 'returns the value' do
507
- expect(mash.fetch(:one)).to eql(1)
508
- end
509
-
510
- it 'returns the value even if the value is falsy' do
511
- expect(mash.fetch(:other)).to eql(false)
512
- end
513
-
514
- context 'when key has other than original but acceptable type' do
515
- it 'returns the value' do
516
- expect(mash.fetch('one')).to eql(1)
517
- end
518
- end
519
- end
520
-
521
- context 'when key does not exist' do
522
- it 'raises KeyError' do
523
- error = RUBY_VERSION =~ /1.8/ ? IndexError : KeyError
524
- expect { mash.fetch(:two) }.to raise_error(error)
525
- end
526
-
527
- context 'with default value given' do
528
- it 'returns default value' do
529
- expect(mash.fetch(:two, 8)).to eql(8)
530
- end
531
-
532
- it 'returns default value even if it is falsy' do
533
- expect(mash.fetch(:two, false)).to eql(false)
534
- end
535
- end
536
-
537
- context 'with block given' do
538
- it 'returns default value' do
539
- expect(mash.fetch(:two) do
540
- 'block default value'
541
- end).to eql('block default value')
542
- end
543
- end
544
- end
545
- end
546
-
547
- describe '#to_hash' do
548
- let(:hash) { { 'outer' => { 'inner' => 42 }, 'testing' => [1, 2, 3] } }
549
- let(:mash) { Hashie::Mash.new(hash) }
550
-
551
- it 'returns a standard Hash' do
552
- expect(mash.to_hash).to be_a(::Hash)
553
- end
554
-
555
- it 'includes all keys' do
556
- expect(mash.to_hash.keys).to eql(%w(outer testing))
557
- end
558
-
559
- it 'converts keys to symbols when symbolize_keys option is true' do
560
- expect(mash.to_hash(symbolize_keys: true).keys).to include(:outer)
561
- expect(mash.to_hash(symbolize_keys: true).keys).not_to include('outer')
562
- end
563
-
564
- it 'leaves keys as strings when symbolize_keys option is false' do
565
- expect(mash.to_hash(symbolize_keys: false).keys).to include('outer')
566
- expect(mash.to_hash(symbolize_keys: false).keys).not_to include(:outer)
567
- end
568
-
569
- it 'symbolizes keys recursively' do
570
- expect(mash.to_hash(symbolize_keys: true)[:outer].keys).to include(:inner)
571
- expect(mash.to_hash(symbolize_keys: true)[:outer].keys).not_to include('inner')
572
- end
573
- end
574
-
575
- describe '#stringify_keys' do
576
- it 'turns all keys into strings recursively' do
577
- hash = Hashie::Mash[:a => 'hey', 123 => { 345 => 'hey' }]
578
- hash.stringify_keys!
579
- expect(hash).to eq Hashie::Hash['a' => 'hey', '123' => { '345' => 'hey' }]
580
- end
581
- end
582
-
583
- describe '#values_at' do
584
- let(:hash) { { 'key_one' => 1, :key_two => 2 } }
585
- let(:mash) { Hashie::Mash.new(hash) }
586
-
587
- context 'when the original type is given' do
588
- it 'returns the values' do
589
- expect(mash.values_at('key_one', :key_two)).to eq([1, 2])
590
- end
591
- end
592
-
593
- context 'when a different, but acceptable type is given' do
594
- it 'returns the values' do
595
- expect(mash.values_at(:key_one, 'key_two')).to eq([1, 2])
596
- end
597
- end
598
-
599
- context 'when a key is given that is not in the Mash' do
600
- it 'returns nil for that value' do
601
- expect(mash.values_at('key_one', :key_three)).to eq([1, nil])
602
- end
603
- end
604
- end
605
-
606
- describe '.load(filename, options = {})' do
607
- let(:config) do
608
- {
609
- 'production' => {
610
- 'foo' => 'production_foo'
611
- }
612
- }
613
- end
614
- let(:path) { 'database.yml' }
615
- let(:parser) { double(:parser) }
616
-
617
- subject { described_class.load(path, parser: parser) }
618
-
619
- before do |ex|
620
- unless ex.metadata == :test_cache
621
- described_class.instance_variable_set('@_mashes', nil) # clean the cached mashes
622
- end
623
- end
624
-
625
- context 'if the file exists' do
626
- before do
627
- expect(File).to receive(:file?).with(path).and_return(true)
628
- expect(parser).to receive(:perform).with(path).and_return(config)
629
- end
630
-
631
- it { is_expected.to be_a(Hashie::Mash) }
632
-
633
- it 'return a Mash from a file' do
634
- expect(subject.production).not_to be_nil
635
- expect(subject.production.keys).to eq config['production'].keys
636
- expect(subject.production.foo).to eq config['production']['foo']
637
- end
638
-
639
- it 'freeze the attribtues' do
640
- expect { subject.production = {} }.to raise_exception(RuntimeError, /can't modify frozen/)
641
- end
642
- end
643
-
644
- context 'if the fils does not exists' do
645
- before do
646
- expect(File).to receive(:file?).with(path).and_return(false)
647
- end
648
-
649
- it 'raise an ArgumentError' do
650
- expect { subject }.to raise_exception(ArgumentError)
651
- end
652
- end
653
-
654
- context 'if the file is passed as Pathname' do
655
- require 'pathname'
656
- let(:path) { Pathname.new('database.yml') }
657
-
658
- before do
659
- expect(File).to receive(:file?).with(path).and_return(true)
660
- expect(parser).to receive(:perform).with(path).and_return(config)
661
- end
662
-
663
- it 'return a Mash from a file' do
664
- expect(subject.production.foo).to eq config['production']['foo']
665
- end
666
- end
667
-
668
- describe 'results are cached' do
669
- let(:parser) { double(:parser) }
670
-
671
- subject { described_class.load(path, parser: parser) }
672
-
673
- before do
674
- expect(File).to receive(:file?).with(path).and_return(true)
675
- expect(File).to receive(:file?).with("#{path}+1").and_return(true)
676
- expect(parser).to receive(:perform).once.with(path).and_return(config)
677
- expect(parser).to receive(:perform).once.with("#{path}+1").and_return(config)
678
- end
679
-
680
- it 'cache the loaded yml file', :test_cache do
681
- 2.times do
682
- expect(subject).to be_a(described_class)
683
- expect(described_class.load("#{path}+1", parser: parser)).to be_a(described_class)
684
- end
685
-
686
- expect(subject.object_id).to eq subject.object_id
687
- end
688
- end
689
- end
690
-
691
- describe '#to_module(mash_method_name)' do
692
- let(:mash) { described_class.new }
693
- subject { Class.new.extend mash.to_module }
694
-
695
- it 'defines a settings method on the klass class that extends the module' do
696
- expect(subject).to respond_to(:settings)
697
- expect(subject.settings).to eq mash
698
- end
699
-
700
- context 'when a settings_method_name is set' do
701
- let(:mash_method_name) { 'config' }
702
-
703
- subject { Class.new.extend mash.to_module(mash_method_name) }
704
-
705
- it 'defines a settings method on the klass class that extends the module' do
706
- expect(subject).to respond_to(mash_method_name.to_sym)
707
- expect(subject.send(mash_method_name.to_sym)).to eq mash
708
- end
709
- end
710
- end
711
-
712
- describe '#extractable_options?' do
713
- require 'active_support'
714
-
715
- subject { described_class.new(name: 'foo') }
716
- let(:args) { [101, 'bar', subject] }
717
-
718
- it 'can be extracted from an array' do
719
- expect(args.extract_options!).to eq subject
720
- expect(args).to eq [101, 'bar']
721
- end
722
- end
723
-
724
- describe '#reverse_merge' do
725
- subject { described_class.new(a: 1, b: 2) }
726
-
727
- it 'unifies strings and symbols' do
728
- expect(subject.reverse_merge(a: 2).length).to eq 2
729
- expect(subject.reverse_merge('a' => 2).length).to eq 2
730
- end
731
-
732
- it 'does not overwrite values' do
733
- expect(subject.reverse_merge(a: 5).a).to eq subject.a
734
- end
735
-
736
- context 'when using with subclass' do
737
- let(:subclass) { Class.new(Hashie::Mash) }
738
- subject { subclass.new(a: 1) }
739
-
740
- it 'creates an instance of subclass' do
741
- expect(subject.reverse_merge(a: 5)).to be_kind_of(subclass)
742
- end
743
- end
744
- end
745
-
746
- with_minimum_ruby('2.3.0') do
747
- describe '#dig' do
748
- subject { described_class.new(a: { b: 1 }) }
749
- it 'accepts both string and symbol as key' do
750
- expect(subject.dig(:a, :b)).to eq(1)
751
- expect(subject.dig('a', 'b')).to eq(1)
752
- end
753
-
754
- context 'with numeric key' do
755
- subject { described_class.new('1' => { b: 1 }) }
756
- it 'accepts a numeric value as key' do
757
- expect(subject.dig(1, :b)).to eq(1)
758
- expect(subject.dig('1', :b)).to eq(1)
759
- end
760
- end
761
- end
762
- end
763
- end