hashie 3.6.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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