hashie 3.4.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +518 -122
  3. data/CONTRIBUTING.md +24 -7
  4. data/LICENSE +1 -1
  5. data/README.md +455 -48
  6. data/Rakefile +18 -1
  7. data/UPGRADING.md +157 -7
  8. data/hashie.gemspec +14 -7
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +56 -31
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +91 -52
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
  17. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  18. data/lib/hashie/extensions/dash/property_translation.rb +59 -30
  19. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  20. data/lib/hashie/extensions/deep_find.rb +14 -5
  21. data/lib/hashie/extensions/deep_locate.rb +40 -21
  22. data/lib/hashie/extensions/deep_merge.rb +28 -10
  23. data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
  24. data/lib/hashie/extensions/indifferent_access.rb +49 -8
  25. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +77 -19
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
  33. data/lib/hashie/extensions/ruby_version.rb +60 -0
  34. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  35. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  36. data/lib/hashie/extensions/stringify_keys.rb +8 -5
  37. data/lib/hashie/extensions/symbolize_keys.rb +21 -7
  38. data/lib/hashie/hash.rb +18 -11
  39. data/lib/hashie/logger.rb +18 -0
  40. data/lib/hashie/mash.rb +196 -55
  41. data/lib/hashie/railtie.rb +21 -0
  42. data/lib/hashie/rash.rb +7 -7
  43. data/lib/hashie/utils.rb +44 -0
  44. data/lib/hashie/version.rb +1 -1
  45. data/lib/hashie.rb +34 -16
  46. metadata +30 -79
  47. data/spec/hashie/clash_spec.rb +0 -48
  48. data/spec/hashie/dash_spec.rb +0 -513
  49. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  50. data/spec/hashie/extensions/coercion_spec.rb +0 -625
  51. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  52. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  53. data/spec/hashie/extensions/deep_find_spec.rb +0 -45
  54. data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
  55. data/spec/hashie/extensions/deep_merge_spec.rb +0 -45
  56. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
  57. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
  58. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  59. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  60. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -23
  61. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  62. data/spec/hashie/extensions/method_access_spec.rb +0 -184
  63. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -101
  64. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -106
  65. data/spec/hashie/hash_spec.rb +0 -84
  66. data/spec/hashie/mash_spec.rb +0 -683
  67. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
  68. data/spec/hashie/rash_spec.rb +0 -77
  69. data/spec/hashie/trash_spec.rb +0 -268
  70. data/spec/hashie/version_spec.rb +0 -7
  71. data/spec/spec_helper.rb +0 -15
  72. data/spec/support/module_context.rb +0 -11
  73. data/spec/support/ruby_version.rb +0 -10
@@ -1,97 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Hashie
4
- module Extensions
5
- describe DeepFetch do
6
- subject { Class.new(Hash) { include Hashie::Extensions::DeepFetch } }
7
- let(:hash) do
8
- {
9
- library: {
10
- books: [
11
- { title: 'Call of the Wild' },
12
- { title: 'Moby Dick' }
13
- ],
14
- shelves: nil,
15
- location: {
16
- address: '123 Library St.'
17
- }
18
- }
19
- }
20
- end
21
- let(:instance) { subject.new.update(hash) }
22
-
23
- describe '#deep_fetch' do
24
- it 'extracts a value from a nested hash' do
25
- expect(instance.deep_fetch(:library, :location, :address)).to eq('123 Library St.')
26
- end
27
-
28
- it 'extracts a value from a nested array' do
29
- expect(instance.deep_fetch(:library, :books, 1, :title)).to eq('Moby Dick')
30
- end
31
-
32
- context 'when one of the keys is not present' do
33
- context 'when a block is provided' do
34
- it 'returns the value of the block' do
35
- value = instance.deep_fetch(:library, :unknown_key, :location) { 'block value' }
36
- expect(value).to eq('block value')
37
- end
38
- end
39
-
40
- context 'when a block is not provided' do
41
- context 'when the nested object is an array' do
42
- it 'raises an UndefinedPathError' do
43
- expect do
44
- instance.deep_fetch(:library, :books, 2)
45
- end.to(
46
- raise_error(
47
- DeepFetch::UndefinedPathError,
48
- 'Could not fetch path (library > books > 2) at 2'
49
- )
50
- )
51
- end
52
- end
53
-
54
- context 'when the nested object is a hash' do
55
- it 'raises a UndefinedPathError' do
56
- expect do
57
- instance.deep_fetch(:library, :location, :unknown_key)
58
- end.to(
59
- raise_error(
60
- DeepFetch::UndefinedPathError,
61
- 'Could not fetch path (library > location > unknown_key) at unknown_key'
62
- )
63
- )
64
- end
65
- end
66
-
67
- context 'when the nested object is missing' do
68
- it 'raises an UndefinedPathError' do
69
- expect do
70
- instance.deep_fetch(:library, :unknown_key, :books)
71
- end.to(
72
- raise_error(
73
- DeepFetch::UndefinedPathError,
74
- 'Could not fetch path (library > unknown_key > books) at unknown_key'
75
- )
76
- )
77
- end
78
- end
79
-
80
- context 'when the nested object is nil' do
81
- it 'raises an UndefinedPathError' do
82
- expect do
83
- instance.deep_fetch(:library, :shelves, :address)
84
- end.to(
85
- raise_error(
86
- DeepFetch::UndefinedPathError,
87
- 'Could not fetch path (library > shelves > address) at address'
88
- )
89
- )
90
- end
91
- end
92
- end
93
- end
94
- end
95
- end
96
- end
97
- end
@@ -1,45 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::DeepFind do
4
- subject { Class.new(Hash) { include Hashie::Extensions::DeepFind } }
5
- let(:hash) do
6
- {
7
- library: {
8
- books: [
9
- { title: 'Call of the Wild' },
10
- { title: 'Moby Dick' }
11
- ],
12
- shelves: nil,
13
- location: {
14
- address: '123 Library St.',
15
- title: 'Main Library'
16
- }
17
- }
18
- }
19
- end
20
- let(:instance) { subject.new.update(hash) }
21
-
22
- describe '#deep_find' do
23
- it 'detects a value from a nested hash' do
24
- expect(instance.deep_find(:address)).to eq('123 Library St.')
25
- end
26
-
27
- it 'detects a value from a nested array' do
28
- expect(instance.deep_find(:title)).to eq('Call of the Wild')
29
- end
30
-
31
- it 'returns nil if it does not find a match' do
32
- expect(instance.deep_find(:wahoo)).to be_nil
33
- end
34
- end
35
-
36
- describe '#deep_find_all' do
37
- it 'detects all values from a nested hash' do
38
- expect(instance.deep_find_all(:title)).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
39
- end
40
-
41
- it 'returns nil if it does not find any matches' do
42
- expect(instance.deep_find_all(:wahoo)).to be_nil
43
- end
44
- end
45
- end
@@ -1,124 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::DeepLocate do
4
- let(:hash) do
5
- {
6
- from: 0,
7
- size: 25,
8
- query: {
9
- bool: {
10
- must: [
11
- {
12
- query_string: {
13
- query: 'foobar',
14
- default_operator: 'AND',
15
- fields: [
16
- 'title^2',
17
- '_all'
18
- ]
19
- }
20
- },
21
- {
22
- match: {
23
- field_1: 'value_1'
24
- }
25
- },
26
- {
27
- range: {
28
- lsr09: {
29
- gte: 2014
30
- }
31
- }
32
- }
33
- ],
34
- should: [
35
- {
36
- match: {
37
- field_2: 'value_2'
38
- }
39
- }
40
- ],
41
- must_not: [
42
- {
43
- range: {
44
- lsr10: {
45
- gte: 2014
46
- }
47
- }
48
- }
49
- ]
50
- }
51
- }
52
- }
53
- end
54
-
55
- describe '.deep_locate' do
56
- context 'if called with a non-callable comparator' do
57
- it 'creates a key comparator on-th-fly' do
58
- expect(described_class.deep_locate(:lsr10, hash)).to eq([hash[:query][:bool][:must_not][0][:range]])
59
- end
60
- end
61
-
62
- it 'locates enumerables for which the given comparator returns true for at least one element' do
63
- examples = [
64
- [
65
- ->(key, _value, _object) { key == :fields },
66
- [
67
- hash[:query][:bool][:must].first[:query_string]
68
- ]
69
- ],
70
- [
71
- ->(_key, value, _object) { value.is_a?(String) && value.include?('value') },
72
- [
73
- hash[:query][:bool][:must][1][:match],
74
- hash[:query][:bool][:should][0][:match]
75
- ]
76
- ],
77
- [
78
- lambda do |_key, _value, object|
79
- object.is_a?(Array) &&
80
- !object.extend(described_class).deep_locate(:match).empty?
81
- end,
82
- [
83
- hash[:query][:bool][:must],
84
- hash[:query][:bool][:should]
85
- ]
86
- ]
87
- ]
88
-
89
- examples.each do |comparator, expected_result|
90
- expect(described_class.deep_locate(comparator, hash)).to eq(expected_result)
91
- end
92
- end
93
-
94
- it 'returns an empty array if nothing was found' do
95
- expect(described_class.deep_locate(:muff, foo: 'bar')).to eq([])
96
- end
97
- end
98
-
99
- context 'if extending an existing object' do
100
- let(:extended_hash) do
101
- hash.extend(described_class)
102
- end
103
-
104
- it 'adds #deep_locate' do
105
- expect(extended_hash.deep_locate(:bool)).to eq([hash[:query]])
106
- end
107
- end
108
-
109
- context 'if included in a hash' do
110
- let(:derived_hash_with_extension_included) do
111
- Class.new(Hash) do
112
- include Hashie::Extensions::DeepLocate
113
- end
114
- end
115
-
116
- let(:instance) do
117
- derived_hash_with_extension_included.new.update(hash)
118
- end
119
-
120
- it 'adds #deep_locate' do
121
- expect(instance.deep_locate(:bool)).to eq([hash[:query]])
122
- end
123
- end
124
- end
@@ -1,45 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::DeepMerge do
4
- class DeepMergeHash < Hash
5
- include Hashie::Extensions::DeepMerge
6
- end
7
-
8
- subject { DeepMergeHash }
9
-
10
- it 'should return initial hash for arguments that are not hash' do
11
- hash = subject.new.merge(a: 'a')
12
- expect(hash.deep_merge('abc')).to eq(hash)
13
- end
14
-
15
- context 'without &block' do
16
- let(:h1) { subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } }) }
17
- let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } } } }
18
- let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } } } }
19
-
20
- it 'deep merges two hashes' do
21
- expect(h1.deep_merge(h2)).to eq expected_hash
22
- end
23
-
24
- it 'deep merges another hash in place via bang method' do
25
- h1.deep_merge!(h2)
26
- expect(h1).to eq expected_hash
27
- end
28
- end
29
-
30
- context 'with &block' do
31
- let(:h1) { subject.new.merge(a: 100, b: 200, c: { c1: 100 }) }
32
- let(:h2) { { b: 250, c: { c1: 200 } } }
33
- let(:expected_hash) { { a: 100, b: 450, c: { c1: 300 } } }
34
- let(:block) { proc { |_, this_val, other_val| this_val + other_val } }
35
-
36
- it 'deep merges two hashes' do
37
- expect(h1.deep_merge(h2, &block)).to eq expected_hash
38
- end
39
-
40
- it 'deep merges another hash in place via bang method' do
41
- h1.deep_merge!(h2, &block)
42
- expect(h1).to eq expected_hash
43
- end
44
- end
45
- end
@@ -1,46 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::IgnoreUndeclared do
4
- context 'included in Trash' do
5
- class ForgivingTrash < Hashie::Trash
6
- include Hashie::Extensions::IgnoreUndeclared
7
- property :city
8
- property :state, from: :provence
9
- end
10
-
11
- subject { ForgivingTrash }
12
-
13
- it 'silently ignores undeclared properties on initialization' do
14
- expect { subject.new(city: 'Toronto', provence: 'ON', country: 'Canada') }.to_not raise_error
15
- end
16
-
17
- it 'works with translated properties (with symbol keys)' do
18
- expect(subject.new(provence: 'Ontario').state).to eq('Ontario')
19
- end
20
-
21
- it 'works with translated properties (with string keys)' do
22
- expect(subject.new(provence: 'Ontario').state).to eq('Ontario')
23
- end
24
-
25
- it 'requires properties to be declared on assignment' do
26
- hash = subject.new(city: 'Toronto')
27
- expect { hash.country = 'Canada' }.to raise_error(NoMethodError)
28
- end
29
- end
30
-
31
- context 'combined with DeepMerge' do
32
- class ForgivingTrashWithMerge < Hashie::Trash
33
- include Hashie::Extensions::DeepMerge
34
- include Hashie::Extensions::IgnoreUndeclared
35
- property :some_key
36
- end
37
-
38
- it 'deep merges' do
39
- class ForgivingTrashWithMergeAndProperty < ForgivingTrashWithMerge
40
- property :some_other_key
41
- end
42
- hash = ForgivingTrashWithMergeAndProperty.new(some_ignored_key: 17, some_key: 12)
43
- expect(hash.deep_merge(some_other_key: 55, some_ignored_key: 18)).to eq(some_key: 12, some_other_key: 55)
44
- end
45
- end
46
- end
@@ -1,219 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::IndifferentAccess do
4
- class IndifferentHashWithMergeInitializer < Hash
5
- include Hashie::Extensions::MergeInitializer
6
- include Hashie::Extensions::IndifferentAccess
7
-
8
- class << self
9
- alias_method :build, :new
10
- end
11
- end
12
-
13
- class IndifferentHashWithArrayInitializer < Hash
14
- include Hashie::Extensions::IndifferentAccess
15
-
16
- class << self
17
- alias_method :build, :[]
18
- end
19
- end
20
-
21
- class IndifferentHashWithTryConvertInitializer < Hash
22
- include Hashie::Extensions::IndifferentAccess
23
-
24
- class << self
25
- alias_method :build, :try_convert
26
- end
27
- end
28
-
29
- class IndifferentHashWithDash < Hashie::Dash
30
- include Hashie::Extensions::IndifferentAccess
31
- property :foo
32
- end
33
-
34
- describe 'when included in dash' do
35
- let(:params) { { foo: 'bar' } }
36
- subject { IndifferentHashWithDash.new(params) }
37
-
38
- it 'initialize with a symbol' do
39
- expect(subject.foo).to eq params[:foo]
40
- end
41
- end
42
-
43
- shared_examples_for 'hash with indifferent access' do
44
- it 'is able to access via string or symbol' do
45
- h = subject.build(abc: 123)
46
- expect(h[:abc]).to eq 123
47
- expect(h['abc']).to eq 123
48
- end
49
-
50
- describe '#values_at' do
51
- it 'indifferently finds values' do
52
- h = subject.build(:foo => 'bar', 'baz' => 'qux')
53
- expect(h.values_at('foo', :baz)).to eq %w(bar qux)
54
- end
55
-
56
- it 'returns the same instance of the hash that was set' do
57
- hash = Hash.new
58
- h = subject.build(foo: hash)
59
- expect(h.values_at(:foo)[0]).to be(hash)
60
- end
61
-
62
- it 'returns the same instance of the array that was set' do
63
- array = Array.new
64
- h = subject.build(foo: array)
65
- expect(h.values_at(:foo)[0]).to be(array)
66
- end
67
-
68
- it 'returns the same instance of the string that was set' do
69
- str = 'my string'
70
- h = subject.build(foo: str)
71
- expect(h.values_at(:foo)[0]).to be(str)
72
- end
73
-
74
- it 'returns the same instance of the object that was set' do
75
- object = Object.new
76
- h = subject.build(foo: object)
77
- expect(h.values_at(:foo)[0]).to be(object)
78
- end
79
- end
80
-
81
- describe '#fetch' do
82
- it 'works like normal fetch, but indifferent' do
83
- h = subject.build(foo: 'bar')
84
- expect(h.fetch(:foo)).to eq h.fetch('foo')
85
- expect(h.fetch(:foo)).to eq 'bar'
86
- end
87
-
88
- it 'returns the same instance of the hash that was set' do
89
- hash = Hash.new
90
- h = subject.build(foo: hash)
91
- expect(h.fetch(:foo)).to be(hash)
92
- end
93
-
94
- it 'returns the same instance of the array that was set' do
95
- array = Array.new
96
- h = subject.build(foo: array)
97
- expect(h.fetch(:foo)).to be(array)
98
- end
99
-
100
- it 'returns the same instance of the string that was set' do
101
- str = 'my string'
102
- h = subject.build(foo: str)
103
- expect(h.fetch(:foo)).to be(str)
104
- end
105
-
106
- it 'returns the same instance of the object that was set' do
107
- object = Object.new
108
- h = subject.build(foo: object)
109
- expect(h.fetch(:foo)).to be(object)
110
- end
111
-
112
- it 'yields with key name if key does not exists' do
113
- h = subject.build(a: 0)
114
- expect(h.fetch(:foo) { |key| ['default for', key] }).to eq ['default for', 'foo']
115
- end
116
- end
117
-
118
- describe '#delete' do
119
- it 'deletes indifferently' do
120
- h = subject.build(:foo => 'bar', 'baz' => 'qux')
121
- h.delete('foo')
122
- h.delete(:baz)
123
- expect(h).to be_empty
124
- end
125
- end
126
-
127
- describe '#key?' do
128
- let(:h) { subject.build(foo: 'bar') }
129
-
130
- it 'finds it indifferently' do
131
- expect(h).to be_key(:foo)
132
- expect(h).to be_key('foo')
133
- end
134
-
135
- %w(include? member? has_key?).each do |key_alias|
136
- it "is aliased as #{key_alias}" do
137
- expect(h.send(key_alias.to_sym, :foo)).to be(true)
138
- expect(h.send(key_alias.to_sym, 'foo')).to be(true)
139
- end
140
- end
141
- end
142
-
143
- describe '#update' do
144
- let(:h) { subject.build(foo: 'bar') }
145
-
146
- it 'allows keys to be indifferent still' do
147
- h.update(baz: 'qux')
148
- expect(h['foo']).to eq 'bar'
149
- expect(h['baz']).to eq 'qux'
150
- end
151
-
152
- it 'recursively injects indifference into sub-hashes' do
153
- h.update(baz: { qux: 'abc' })
154
- expect(h['baz']['qux']).to eq 'abc'
155
- end
156
-
157
- it 'does not change the ancestors of the injected object class' do
158
- h.update(baz: { qux: 'abc' })
159
- expect(Hash.new).not_to be_respond_to(:indifferent_access?)
160
- end
161
- end
162
-
163
- describe '#replace' do
164
- let(:h) { subject.build(foo: 'bar').replace(bar: 'baz', hi: 'bye') }
165
-
166
- it 'returns self' do
167
- expect(h).to be_a(subject)
168
- end
169
-
170
- it 'removes old keys' do
171
- [:foo, 'foo'].each do |k|
172
- expect(h[k]).to be_nil
173
- expect(h.key?(k)).to be_falsy
174
- end
175
- end
176
-
177
- it 'creates new keys with indifferent access' do
178
- [:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be_truthy }
179
- expect(h[:bar]).to eq 'baz'
180
- expect(h['bar']).to eq 'baz'
181
- expect(h[:hi]).to eq 'bye'
182
- expect(h['hi']).to eq 'bye'
183
- end
184
- end
185
-
186
- describe '#try_convert' do
187
- describe 'with conversion' do
188
- let(:h) { subject.try_convert(foo: 'bar') }
189
-
190
- it 'is a subject' do
191
- expect(h).to be_a(subject)
192
- end
193
- end
194
-
195
- describe 'without conversion' do
196
- let(:h) { subject.try_convert('{ :foo => bar }') }
197
-
198
- it 'is nil' do
199
- expect(h).to be_nil
200
- end
201
- end
202
- end
203
- end
204
-
205
- describe 'with merge initializer' do
206
- subject { IndifferentHashWithMergeInitializer }
207
- it_should_behave_like 'hash with indifferent access'
208
- end
209
-
210
- describe 'with array initializer' do
211
- subject { IndifferentHashWithArrayInitializer }
212
- it_should_behave_like 'hash with indifferent access'
213
- end
214
-
215
- describe 'with try convert initializer' do
216
- subject { IndifferentHashWithTryConvertInitializer }
217
- it_should_behave_like 'hash with indifferent access'
218
- end
219
- end