hashie 3.6.0 → 4.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +95 -7
  4. data/UPGRADING.md +78 -2
  5. data/hashie.gemspec +2 -1
  6. data/lib/hashie.rb +20 -19
  7. data/lib/hashie/dash.rb +2 -1
  8. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  9. data/lib/hashie/extensions/coercion.rb +23 -16
  10. data/lib/hashie/extensions/dash/indifferent_access.rb +20 -1
  11. data/lib/hashie/extensions/dash/property_translation.rb +5 -2
  12. data/lib/hashie/extensions/deep_fetch.rb +4 -2
  13. data/lib/hashie/extensions/deep_find.rb +12 -3
  14. data/lib/hashie/extensions/deep_locate.rb +22 -7
  15. data/lib/hashie/extensions/indifferent_access.rb +1 -3
  16. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  17. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  18. data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -1
  19. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  20. data/lib/hashie/extensions/method_access.rb +5 -2
  21. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +9 -4
  22. data/lib/hashie/extensions/strict_key_access.rb +8 -4
  23. data/lib/hashie/hash.rb +16 -9
  24. data/lib/hashie/mash.rb +99 -43
  25. data/lib/hashie/railtie.rb +7 -0
  26. data/lib/hashie/rash.rb +1 -1
  27. data/lib/hashie/version.rb +1 -1
  28. data/spec/hashie/dash_spec.rb +18 -8
  29. data/spec/hashie/extensions/coercion_spec.rb +17 -8
  30. data/spec/hashie/extensions/deep_find_spec.rb +12 -6
  31. data/spec/hashie/extensions/deep_locate_spec.rb +2 -1
  32. data/spec/hashie/extensions/deep_merge_spec.rb +6 -2
  33. data/spec/hashie/extensions/ignore_undeclared_spec.rb +2 -1
  34. data/spec/hashie/extensions/mash/define_accessors_spec.rb +90 -0
  35. data/spec/hashie/extensions/method_access_spec.rb +8 -1
  36. data/spec/hashie/extensions/strict_key_access_spec.rb +9 -10
  37. data/spec/hashie/extensions/symbolize_keys_spec.rb +3 -1
  38. data/spec/hashie/hash_spec.rb +45 -6
  39. data/spec/hashie/mash_spec.rb +314 -8
  40. data/spec/hashie/trash_spec.rb +9 -3
  41. data/spec/integration/elasticsearch/integration_spec.rb +3 -2
  42. data/spec/integration/rails/app.rb +5 -12
  43. data/spec/integration/rails/integration_spec.rb +22 -1
  44. metadata +8 -4
@@ -41,7 +41,8 @@ describe Hashie::Extensions::IgnoreUndeclared do
41
41
  property :some_other_key
42
42
  end
43
43
  hash = ForgivingTrashWithMergeAndProperty.new(some_ignored_key: 17, some_key: 12)
44
- expect(hash.deep_merge(some_other_key: 55, some_ignored_key: 18)).to eq(some_key: 12, some_other_key: 55)
44
+ expect(hash.deep_merge(some_other_key: 55, some_ignored_key: 18))
45
+ .to eq(some_key: 12, some_other_key: 55)
45
46
  end
46
47
  end
47
48
  end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hashie::Extensions::Mash::DefineAccessors do
4
+ let(:args) { [] }
5
+
6
+ shared_examples 'class with dynamically defined accessors' do
7
+ it 'defines reader on demand' do
8
+ expect(subject.method_defined?(:foo)).to be_falsey
9
+ instance.foo
10
+ expect(subject.method_defined?(:foo)).to be_truthy
11
+ end
12
+
13
+ it 'defines writer on demand' do
14
+ expect(subject.method_defined?(:foo=)).to be_falsey
15
+ instance.foo = :bar
16
+ expect(subject.method_defined?(:foo=)).to be_truthy
17
+ end
18
+
19
+ it 'defines predicate on demand' do
20
+ expect(subject.method_defined?(:foo?)).to be_falsey
21
+ instance.foo?
22
+ expect(subject.method_defined?(:foo?)).to be_truthy
23
+ end
24
+
25
+ it 'defines initializing reader on demand' do
26
+ expect(subject.method_defined?(:foo!)).to be_falsey
27
+ instance.foo!
28
+ expect(subject.method_defined?(:foo!)).to be_truthy
29
+ end
30
+
31
+ it 'defines underbang reader on demand' do
32
+ expect(subject.method_defined?(:foo_)).to be_falsey
33
+ instance.foo_
34
+ expect(subject.method_defined?(:foo_)).to be_truthy
35
+ end
36
+
37
+ context 'when initializing from another hash' do
38
+ let(:args) { [{ foo: :bar }] }
39
+
40
+ it 'does not define any accessors' do
41
+ expect(subject.method_defined?(:foo)).to be_falsey
42
+ expect(subject.method_defined?(:foo=)).to be_falsey
43
+ expect(subject.method_defined?(:foo?)).to be_falsey
44
+ expect(subject.method_defined?(:foo!)).to be_falsey
45
+ expect(subject.method_defined?(:foo_)).to be_falsey
46
+ expect(instance.foo).to eq :bar
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when included in Mash subclass' do
52
+ subject { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
53
+ let(:instance) { subject.new(*args) }
54
+
55
+ describe 'this subclass' do
56
+ it_behaves_like 'class with dynamically defined accessors'
57
+
58
+ describe 'when accessors are overrided in class' do
59
+ before do
60
+ subject.class_eval do
61
+ def foo
62
+ if self[:foo] != 1
63
+ :bar
64
+ else
65
+ super
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ it 'allows to call super' do
72
+ expect(instance.foo).to eq :bar
73
+ instance.foo = 2
74
+ expect(instance.foo).to eq :bar
75
+ instance.foo = 1
76
+ expect(instance.foo).to eq 1
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'when Mash instance is extended' do
83
+ let(:instance) { Hashie::Mash.new(*args).with_accessors! }
84
+ subject { instance.singleton_class }
85
+
86
+ describe 'its singleton class' do
87
+ it_behaves_like 'class with dynamically defined accessors'
88
+ end
89
+ end
90
+ end
@@ -128,7 +128,14 @@ describe Hashie::Extensions::MethodAccess do
128
128
  it 'includes all of the other method mixins' do
129
129
  klass = Class.new(Hash)
130
130
  klass.send :include, Hashie::Extensions::MethodAccess
131
- expect((klass.ancestors & [Hashie::Extensions::MethodReader, Hashie::Extensions::MethodWriter, Hashie::Extensions::MethodQuery]).size).to eq 3
131
+
132
+ included_modules = klass.ancestors & [
133
+ Hashie::Extensions::MethodReader,
134
+ Hashie::Extensions::MethodWriter,
135
+ Hashie::Extensions::MethodQuery
136
+ ]
137
+
138
+ expect(included_modules.size).to eq 3
132
139
  end
133
140
  end
134
141
 
@@ -35,34 +35,33 @@ describe Hashie::Extensions::StrictKeyAccess do
35
35
  context 'lookup' do
36
36
  it('raises an error') do
37
37
  # Formatting of the error message does not vary here because raised by StrictKeyAccess
38
- expect { instance.key(invalid_value) }.to raise_error KeyError,
39
- %(key not found with value of #{invalid_value.inspect})
38
+ expect { instance.key(invalid_value) }.to raise_error KeyError
40
39
  end
41
40
  end
42
41
  end
43
42
  shared_examples_for 'StrictKeyAccess raises KeyError instead of allowing defaults' do
44
43
  context '#default' do
45
44
  it 'raises an error' do
46
- expect { instance.default(invalid_key) }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
47
- 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
45
+ expect { instance.default(invalid_key) }
46
+ .to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError
48
47
  end
49
48
  end
50
49
  context '#default=' do
51
50
  it 'raises an error' do
52
- expect { instance.default = invalid_key }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
53
- 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
51
+ expect { instance.default = invalid_key }
52
+ .to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError
54
53
  end
55
54
  end
56
55
  context '#default_proc' do
57
56
  it 'raises an error' do
58
- expect { instance.default_proc }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
59
- 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
57
+ expect { instance.default_proc }
58
+ .to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError
60
59
  end
61
60
  end
62
61
  context '#default_proc=' do
63
62
  it 'raises an error' do
64
- expect { instance.default_proc = proc {} }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
65
- 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
63
+ expect { instance.default_proc = proc {} }
64
+ .to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError
66
65
  end
67
66
  end
68
67
  end
@@ -88,7 +88,9 @@ describe Hashie::Extensions::SymbolizeKeys do
88
88
 
89
89
  context 'singleton methods' do
90
90
  subject { Hash }
91
- let(:object) { subject.new.merge('a' => 1, 'b' => { 'c' => 2 }).extend(Hashie::Extensions::SymbolizeKeys) }
91
+ let(:object) do
92
+ subject.new.merge('a' => 1, 'b' => { 'c' => 2 }).extend(Hashie::Extensions::SymbolizeKeys)
93
+ end
92
94
  let(:expected_hash) { { a: 1, b: { c: 2 } } }
93
95
 
94
96
  describe '.symbolize_keys' do
@@ -64,21 +64,60 @@ describe Hash do
64
64
  end
65
65
 
66
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]
67
+ hash = Hashie::Hash[
68
+ 'a' => 'hey',
69
+ 123 => 'bob',
70
+ 'array' => [1, 2, 3],
71
+ subhash: ClassRespondsToHash.new
72
+ ]
68
73
  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] })
74
+
75
+ expected = {
76
+ 'a' => 'hey',
77
+ 123 => 'bob',
78
+ 'array' => [1, 2, 3],
79
+ subhash: { 'a' => 'hey', b: 'bar', 123 => 'bob', 'array' => [1, 2, 3] }
80
+ }
81
+
82
+ expect(stringified_hash).to eq(expected)
70
83
  end
71
84
 
72
85
  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]
86
+ hash = Hashie::Hash[
87
+ 'a' => 'hey',
88
+ 123 => 'bob',
89
+ 'array' => [1, 2, 3],
90
+ subhash: ClassRespondsToHash.new
91
+ ]
74
92
  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] })
93
+
94
+ expected = {
95
+ 'a' => 'hey',
96
+ '123' => 'bob',
97
+ 'array' => [1, 2, 3],
98
+ 'subhash' => { 'a' => 'hey', 'b' => 'bar', '123' => 'bob', 'array' => [1, 2, 3] }
99
+ }
100
+
101
+ expect(symbolized_hash).to eq(expected)
76
102
  end
77
103
 
78
104
  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]
105
+ hash = Hashie::Hash[
106
+ 'a' => 'hey',
107
+ 123 => 'bob',
108
+ 'array' => [1, 2, 3],
109
+ subhash: ClassRespondsToHash.new
110
+ ]
80
111
  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] })
112
+
113
+ expected = {
114
+ a: 'hey',
115
+ :"123" => 'bob',
116
+ array: [1, 2, 3],
117
+ subhash: { a: 'hey', b: 'bar', :'123' => 'bob', array: [1, 2, 3] }
118
+ }
119
+
120
+ expect(symbolized_hash).to eq(expected)
82
121
  end
83
122
  end
84
123
  end
@@ -154,14 +154,15 @@ describe Hashie::Mash do
154
154
  mash_class = Class.new(Hashie::Mash) do
155
155
  disable_warnings
156
156
  end
157
-
158
157
  mash_class.new('trust' => { 'two' => 2 })
159
158
 
160
159
  expect(logger_output).to be_blank
161
160
  end
162
161
 
163
162
  it 'cannot disable logging on the base Mash' do
164
- expect { Hashie::Mash.disable_warnings }.to raise_error(Hashie::Mash::CannotDisableMashWarnings)
163
+ expected_error = Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings
164
+
165
+ expect { Hashie::Mash.disable_warnings }.to raise_error(expected_error)
165
166
  end
166
167
 
167
168
  it 'carries over the disable for warnings on grandchild classes' do
@@ -174,6 +175,78 @@ describe Hashie::Mash do
174
175
 
175
176
  expect(logger_output).to be_blank
176
177
  end
178
+
179
+ it 'writes to logger when a key is overridden that is not ignored' do
180
+ mash_class = Class.new(Hashie::Mash) do
181
+ disable_warnings :merge
182
+ end
183
+
184
+ mash_class.new('address' => { 'zip' => '90210' })
185
+ expect(logger_output).not_to be_blank
186
+ end
187
+
188
+ it 'does not write to logger when a key is overridden that is ignored' do
189
+ mash_class = Class.new(Hashie::Mash) do
190
+ disable_warnings :zip
191
+ end
192
+
193
+ mash_class.new('address' => { 'zip' => '90210' })
194
+ expect(logger_output).to be_blank
195
+ end
196
+
197
+ it 'carries over the ignored warnings list for warnings on grandchild classes' do
198
+ child_class = Class.new(Hashie::Mash) do
199
+ disable_warnings :zip, :merge
200
+ end
201
+ grandchild_class = Class.new(child_class)
202
+
203
+ grandchild_class.new('address' => { 'zip' => '90210' }, 'merge' => true)
204
+
205
+ expect(grandchild_class.disabled_warnings).to eq(%i[zip merge])
206
+ expect(logger_output).to be_blank
207
+ end
208
+
209
+ context 'multiple disable_warnings calls' do
210
+ context 'calling disable_warnings multiple times with parameters' do
211
+ it 'appends each new parameter to the ignore list' do
212
+ child_class = Class.new(Hashie::Mash) do
213
+ disable_warnings :zip
214
+ disable_warnings :merge
215
+ disable_warnings :cycle
216
+ end
217
+
218
+ expect(child_class.disabled_warnings).to eq(%i[zip merge cycle])
219
+ end
220
+ end
221
+
222
+ context 'calling disable_warnings without keys after calling with keys' do
223
+ it 'uses the last call to determine the ignore list' do
224
+ child_class = Class.new(Hashie::Mash) do
225
+ disable_warnings :zip
226
+ disable_warnings
227
+ end
228
+
229
+ child_class.new('address' => { 'zip' => '90210' }, 'merge' => true, 'cycle' => 'bi')
230
+
231
+ expect(child_class.disabled_warnings).to eq([])
232
+ expect(logger_output).to be_blank
233
+ end
234
+ end
235
+
236
+ context 'calling disable_parameters with keys after calling without keys' do
237
+ it 'only ignores logging for ignored methods' do
238
+ child_class = Class.new(Hashie::Mash) do
239
+ disable_warnings
240
+ disable_warnings :zip
241
+ end
242
+
243
+ child_class.new('address' => { 'zip' => '90210' }, 'merge' => true)
244
+
245
+ expect(logger_output).to match(/#{child_class}#merge/)
246
+ expect(logger_output).not_to match(/#{child_class}#zip/)
247
+ end
248
+ end
249
+ end
177
250
  end
178
251
 
179
252
  context 'updating' do
@@ -229,15 +302,29 @@ describe Hashie::Mash do
229
302
 
230
303
  # http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-update
231
304
  it 'accepts a block' do
232
- duped = subject.merge(details: { address: 'Pasadena CA' }) { |_, oldv, newv| [oldv, newv].join(', ') }
305
+ duped = subject.merge(details: { address: 'Pasadena CA' }) do |_, oldv, newv|
306
+ [oldv, newv].join(', ')
307
+ end
308
+
233
309
  expect(duped.details.address).to eq 'Nowhere road, Pasadena CA'
234
310
  end
235
311
 
236
312
  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 }
313
+ m_hash = { details: { address: 'Pasadena CA', state: 'West Thoughtleby' } }
314
+ duped = subject.merge(m_hash) { |_, oldv, _| oldv }
315
+
238
316
  expect(duped.details.address).to eq 'Nowhere road'
239
317
  expect(duped.details.state).to eq 'West Thoughtleby'
240
318
  end
319
+
320
+ it 'does not raise an exception when default_proc raises an error' do
321
+ hash = described_class.new(a: 1) { |_k, _v| raise('Should not be raise I') }
322
+ other_has = described_class.new(a: 2, b: 2) { |_k, _v| raise('Should not be raise II') }
323
+ expected_hash = described_class.new(a: 2, b: 2)
324
+
325
+ res = hash.merge(other_has)
326
+ expect(res).to eq(expected_hash)
327
+ end
241
328
  end
242
329
 
243
330
  describe 'shallow update' do
@@ -633,7 +720,7 @@ describe Hashie::Mash do
633
720
  context 'if the file exists' do
634
721
  before do
635
722
  expect(File).to receive(:file?).with(path).and_return(true)
636
- expect(parser).to receive(:perform).with(path).and_return(config)
723
+ expect(parser).to receive(:perform).with(path, {}).and_return(config)
637
724
  end
638
725
 
639
726
  it { is_expected.to be_a(Hashie::Mash) }
@@ -665,7 +752,7 @@ describe Hashie::Mash do
665
752
 
666
753
  before do
667
754
  expect(File).to receive(:file?).with(path).and_return(true)
668
- expect(parser).to receive(:perform).with(path).and_return(config)
755
+ expect(parser).to receive(:perform).with(path, {}).and_return(config)
669
756
  end
670
757
 
671
758
  it 'return a Mash from a file' do
@@ -681,8 +768,8 @@ describe Hashie::Mash do
681
768
  before do
682
769
  expect(File).to receive(:file?).with(path).and_return(true)
683
770
  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)
771
+ expect(parser).to receive(:perform).once.with(path, {}).and_return(config)
772
+ expect(parser).to receive(:perform).once.with("#{path}+1", {}).and_return(config)
686
773
  end
687
774
 
688
775
  it 'cache the loaded yml file', :test_cache do
@@ -694,6 +781,50 @@ describe Hashie::Mash do
694
781
  expect(subject.object_id).to eq subject.object_id
695
782
  end
696
783
  end
784
+
785
+ context 'when the file has aliases in it' do
786
+ it 'can use the aliases and does not raise an error' do
787
+ mash = Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml')
788
+ expect(mash.company_a.accounts.admin.password).to eq('secret')
789
+ end
790
+ it 'can override the value of aliases' do
791
+ expect do
792
+ Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml', aliases: false)
793
+ end.to raise_error Psych::BadAlias, /base_accounts/
794
+ end
795
+ end
796
+
797
+ context 'when the file has symbols' do
798
+ it 'can override the value of permitted_classes' do
799
+ mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml', permitted_classes: [Symbol])
800
+ expect(mash.user_icon.width).to eq(200)
801
+ end
802
+ it 'uses defaults for permitted_classes' do
803
+ expect do
804
+ Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml')
805
+ end.to raise_error Psych::DisallowedClass, /Symbol/
806
+ end
807
+ it 'can override the value of permitted_symbols' do
808
+ mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
809
+ permitted_classes: [Symbol],
810
+ permitted_symbols: %i[
811
+ user_icon
812
+ width
813
+ height
814
+ ])
815
+ expect(mash.user_icon.width).to eq(200)
816
+ end
817
+ it 'raises an error on insufficient permitted_symbols' do
818
+ expect do
819
+ Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
820
+ permitted_classes: [Symbol],
821
+ permitted_symbols: %i[
822
+ user_icon
823
+ width
824
+ ])
825
+ end.to raise_error Psych::DisallowedClass, /Symbol/
826
+ end
827
+ end
697
828
  end
698
829
 
699
830
  describe '#to_module(mash_method_name)' do
@@ -751,6 +882,103 @@ describe Hashie::Mash do
751
882
  end
752
883
  end
753
884
 
885
+ describe '#compact' do
886
+ subject(:mash) { described_class.new(a: 1, b: nil) }
887
+
888
+ it 'returns a Hashie::Mash' do
889
+ expect(mash.compact).to be_kind_of(described_class)
890
+ end
891
+
892
+ it 'removes keys with nil values' do
893
+ expect(mash.compact).to eq('a' => 1)
894
+ end
895
+
896
+ context 'when using with subclass' do
897
+ let(:subclass) { Class.new(Hashie::Mash) }
898
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
899
+
900
+ it 'creates an instance of subclass' do
901
+ expect(sub_mash.compact).to be_kind_of(subclass)
902
+ end
903
+ end
904
+ end
905
+
906
+ describe '#invert' do
907
+ subject(:mash) { described_class.new(a: 'apple', b: 4) }
908
+
909
+ it 'returns a Hashie::Mash' do
910
+ expect(mash.invert).to be_kind_of(described_class)
911
+ end
912
+
913
+ it 'returns a mash with the keys and values inverted' do
914
+ expect(mash.invert).to eq('apple' => 'a', '4' => 'b')
915
+ end
916
+
917
+ context 'when using with subclass' do
918
+ let(:subclass) { Class.new(Hashie::Mash) }
919
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
920
+
921
+ it 'creates an instance of subclass' do
922
+ expect(sub_mash.invert).to be_kind_of(subclass)
923
+ end
924
+ end
925
+ end
926
+
927
+ describe '#reject' do
928
+ subject(:mash) { described_class.new(a: 1, b: nil) }
929
+
930
+ it 'returns a Hashie::Mash' do
931
+ expect(mash.reject { |_k, v| v.nil? }).to be_kind_of(described_class)
932
+ end
933
+
934
+ it 'rejects keys for which the block returns true' do
935
+ expect(mash.reject { |_k, v| v.nil? }).to eq('a' => 1)
936
+ end
937
+
938
+ context 'when using with subclass' do
939
+ let(:subclass) { Class.new(Hashie::Mash) }
940
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
941
+
942
+ it 'creates an instance of subclass' do
943
+ expect(sub_mash.reject { |_k, v| v.nil? }).to be_kind_of(subclass)
944
+ end
945
+ end
946
+ end
947
+
948
+ describe '#select' do
949
+ subject(:mash) { described_class.new(a: 'apple', b: 4) }
950
+
951
+ it 'returns a Hashie::Mash' do
952
+ expect(mash.select { |_k, v| v.is_a? String }).to be_kind_of(described_class)
953
+ end
954
+
955
+ it 'selects keys for which the block returns true' do
956
+ expect(mash.select { |_k, v| v.is_a? String }).to eq('a' => 'apple')
957
+ end
958
+
959
+ context 'when using with subclass' do
960
+ let(:subclass) { Class.new(Hashie::Mash) }
961
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
962
+
963
+ it 'creates an instance of subclass' do
964
+ expect(sub_mash.select { |_k, v| v.is_a? String }).to be_kind_of(subclass)
965
+ end
966
+ end
967
+ end
968
+
969
+ describe '.quiet' do
970
+ it 'returns a subclass of the calling class' do
971
+ expect(Hashie::Mash.quiet.new).to be_a(Hashie::Mash)
972
+ end
973
+
974
+ it 'memoizes and returns classes' do
975
+ call_one = Hashie::Mash.quiet
976
+ call_two = Hashie::Mash.quiet
977
+ expect(Hashie::Mash.instance_variable_get('@memoized_classes').count).to eq(1)
978
+ expect(call_one).to eq(call_two)
979
+ end
980
+ end
981
+
754
982
  with_minimum_ruby('2.3.0') do
755
983
  describe '#dig' do
756
984
  subject { described_class.new(a: { b: 1 }) }
@@ -768,4 +996,82 @@ describe Hashie::Mash do
768
996
  end
769
997
  end
770
998
  end
999
+
1000
+ with_minimum_ruby('2.4.0') do
1001
+ describe '#transform_values' do
1002
+ subject(:mash) { described_class.new(a: 1) }
1003
+
1004
+ it 'returns a Hashie::Mash' do
1005
+ expect(mash.transform_values(&:to_s)).to be_kind_of(described_class)
1006
+ end
1007
+
1008
+ it 'transforms the value' do
1009
+ expect(mash.transform_values(&:to_s).a).to eql('1')
1010
+ end
1011
+
1012
+ context 'when using with subclass' do
1013
+ let(:subclass) { Class.new(Hashie::Mash) }
1014
+ subject(:sub_mash) { subclass.new(a: 1).transform_values { |a| a + 2 } }
1015
+
1016
+ it 'creates an instance of subclass' do
1017
+ expect(sub_mash).to be_kind_of(subclass)
1018
+ end
1019
+ end
1020
+ end
1021
+ end
1022
+
1023
+ with_minimum_ruby('2.5.0') do
1024
+ describe '#slice' do
1025
+ subject(:mash) { described_class.new(a: 1, b: 2) }
1026
+
1027
+ it 'returns a Hashie::Mash' do
1028
+ expect(mash.slice(:a)).to be_kind_of(described_class)
1029
+ end
1030
+
1031
+ it 'returns a Mash with only the keys passed' do
1032
+ expect(mash.slice(:a).to_hash).to eq('a' => 1)
1033
+ end
1034
+
1035
+ context 'when using with subclass' do
1036
+ let(:subclass) { Class.new(Hashie::Mash) }
1037
+ subject(:sub_mash) { subclass.new(a: 1, b: 2) }
1038
+
1039
+ it 'creates an instance of subclass' do
1040
+ expect(sub_mash.slice(:a)).to be_kind_of(subclass)
1041
+ end
1042
+ end
1043
+ end
1044
+
1045
+ describe '#transform_keys' do
1046
+ subject(:mash) { described_class.new(a: 1, b: 2) }
1047
+
1048
+ it 'returns a Hashie::Mash' do
1049
+ expect(mash.transform_keys { |k| k + k }).to be_kind_of(described_class)
1050
+ end
1051
+
1052
+ it 'returns a Mash with transformed keys' do
1053
+ expect(mash.transform_keys { |k| k + k }).to eq('aa' => 1, 'bb' => 2)
1054
+ end
1055
+
1056
+ context 'when using with subclass' do
1057
+ let(:subclass) { Class.new(Hashie::Mash) }
1058
+ subject(:sub_mash) { subclass.new(a: 1, b: 2) }
1059
+
1060
+ it 'creates an instance of subclass' do
1061
+ expect(sub_mash.transform_keys { |k| k + k }).to be_kind_of(subclass)
1062
+ end
1063
+ end
1064
+ end
1065
+ end
1066
+
1067
+ with_minimum_ruby('2.6.0') do
1068
+ context 'ruby 2.6 merging' do
1069
+ subject(:mash) { Hashie::Mash.new(model: 'Honda') }
1070
+ it 'merges multiple hashes and mashes passeed to #merge' do
1071
+ first_hash = { model: 'Ford' }
1072
+ second_hash = { model: 'DeLorean' }
1073
+ expect(mash.merge(first_hash, second_hash)).to eq('model' => 'DeLorean')
1074
+ end
1075
+ end
1076
+ end
771
1077
  end