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