hashie 2.1.2 → 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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +524 -59
  3. data/CONTRIBUTING.md +24 -7
  4. data/README.md +781 -90
  5. data/Rakefile +19 -2
  6. data/UPGRADING.md +245 -0
  7. data/hashie.gemspec +21 -13
  8. data/lib/hashie.rb +60 -21
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +96 -33
  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 +124 -18
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
  17. data/lib/hashie/extensions/dash/property_translation.rb +191 -0
  18. data/lib/hashie/extensions/deep_fetch.rb +7 -5
  19. data/lib/hashie/extensions/deep_find.rb +69 -0
  20. data/lib/hashie/extensions/deep_locate.rb +113 -0
  21. data/lib/hashie/extensions/deep_merge.rb +35 -12
  22. data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
  23. data/lib/hashie/extensions/indifferent_access.rb +28 -16
  24. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  25. data/lib/hashie/extensions/key_conversion.rb +0 -82
  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 +18 -0
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +154 -11
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
  33. data/lib/hashie/extensions/pretty_inspect.rb +19 -0
  34. data/lib/hashie/extensions/ruby_version.rb +60 -0
  35. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  36. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  37. data/lib/hashie/extensions/stringify_keys.rb +71 -0
  38. data/lib/hashie/extensions/symbolize_keys.rb +71 -0
  39. data/lib/hashie/hash.rb +27 -8
  40. data/lib/hashie/logger.rb +18 -0
  41. data/lib/hashie/mash.rb +235 -57
  42. data/lib/hashie/railtie.rb +21 -0
  43. data/lib/hashie/rash.rb +40 -16
  44. data/lib/hashie/trash.rb +2 -88
  45. data/lib/hashie/utils.rb +44 -0
  46. data/lib/hashie/version.rb +1 -1
  47. metadata +42 -81
  48. data/.gitignore +0 -9
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -36
  51. data/.travis.yml +0 -15
  52. data/Gemfile +0 -11
  53. data/Guardfile +0 -5
  54. data/lib/hashie/hash_extensions.rb +0 -47
  55. data/spec/hashie/clash_spec.rb +0 -48
  56. data/spec/hashie/dash_spec.rb +0 -338
  57. data/spec/hashie/extensions/coercion_spec.rb +0 -156
  58. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
  59. data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
  60. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
  61. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
  62. data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
  63. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  64. data/spec/hashie/extensions/method_access_spec.rb +0 -121
  65. data/spec/hashie/hash_spec.rb +0 -66
  66. data/spec/hashie/mash_spec.rb +0 -467
  67. data/spec/hashie/rash_spec.rb +0 -44
  68. data/spec/hashie/trash_spec.rb +0 -193
  69. data/spec/hashie/version_spec.rb +0 -7
  70. data/spec/spec.opts +0 -3
  71. data/spec/spec_helper.rb +0 -8
@@ -1,156 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::Coercion do
4
- class Initializable
5
- attr_reader :coerced, :value
6
-
7
- def initialize(obj, coerced = false)
8
- @coerced = coerced
9
- @value = obj.class.to_s
10
- end
11
-
12
- def coerced?
13
- !!@coerced
14
- end
15
- end
16
-
17
- class Coercable < Initializable
18
- def self.coerce(obj)
19
- new(obj, true)
20
- end
21
- end
22
-
23
- before(:each) do
24
- class ExampleCoercableHash < Hash
25
- include Hashie::Extensions::Coercion
26
- include Hashie::Extensions::MergeInitializer
27
- end
28
- end
29
-
30
- subject { ExampleCoercableHash }
31
-
32
- let(:instance) { subject.new }
33
-
34
- describe '#coerce_key' do
35
- it { expect(subject).to be_respond_to(:coerce_key) }
36
-
37
- it 'runs through coerce on a specified key' do
38
- subject.coerce_key :foo, Coercable
39
-
40
- instance[:foo] = 'bar'
41
- expect(instance[:foo]).to be_coerced
42
- end
43
-
44
- it 'supports an array of keys' do
45
- subject.coerce_keys :foo, :bar, Coercable
46
-
47
- instance[:foo] = 'bar'
48
- instance[:bar] = 'bax'
49
- expect(instance[:foo]).to be_coerced
50
- expect(instance[:bar]).to be_coerced
51
- end
52
-
53
- it 'calls #new if no coerce method is available' do
54
- subject.coerce_key :foo, Initializable
55
-
56
- instance[:foo] = 'bar'
57
- expect(instance[:foo].value).to eq 'String'
58
- expect(instance[:foo]).not_to be_coerced
59
- end
60
-
61
- it 'coerces when the merge initializer is used' do
62
- subject.coerce_key :foo, Coercable
63
- instance = subject.new(foo: 'bar')
64
-
65
- expect(instance[:foo]).to be_coerced
66
- end
67
-
68
- context 'when #replace is used' do
69
- before { subject.coerce_key :foo, :bar, Coercable }
70
-
71
- let(:instance) do
72
- subject.new(foo: 'bar').replace(foo: 'foz', bar: 'baz', hi: 'bye')
73
- end
74
-
75
- it 'coerces relevant keys' do
76
- expect(instance[:foo]).to be_coerced
77
- expect(instance[:bar]).to be_coerced
78
- expect(instance[:hi]).not_to respond_to(:coerced?)
79
- end
80
-
81
- it 'sets correct values' do
82
- expect(instance[:hi]).to eq 'bye'
83
- end
84
- end
85
-
86
- context 'when used with a Mash' do
87
- class UserMash < Hashie::Mash
88
- end
89
- class TweetMash < Hashie::Mash
90
- include Hashie::Extensions::Coercion
91
- coerce_key :user, UserMash
92
- end
93
-
94
- it 'coerces with instance initialization' do
95
- tweet = TweetMash.new(user: { email: 'foo@bar.com' })
96
- expect(tweet[:user]).to be_a(UserMash)
97
- end
98
-
99
- it 'coerces when setting with attribute style' do
100
- tweet = TweetMash.new
101
- tweet.user = { email: 'foo@bar.com' }
102
- expect(tweet[:user]).to be_a(UserMash)
103
- end
104
-
105
- it 'coerces when setting with string index' do
106
- tweet = TweetMash.new
107
- tweet['user'] = { email: 'foo@bar.com' }
108
- expect(tweet[:user]).to be_a(UserMash)
109
- end
110
-
111
- it 'coerces when setting with symbol index' do
112
- tweet = TweetMash.new
113
- tweet[:user] = { email: 'foo@bar.com' }
114
- expect(tweet[:user]).to be_a(UserMash)
115
- end
116
- end
117
- end
118
-
119
- describe '#coerce_value' do
120
- context 'with strict: true' do
121
- it 'coerces any value of the exact right class' do
122
- subject.coerce_value String, Coercable
123
-
124
- instance[:foo] = 'bar'
125
- instance[:bar] = 'bax'
126
- instance[:hi] = :bye
127
- expect(instance[:foo]).to be_coerced
128
- expect(instance[:bar]).to be_coerced
129
- expect(instance[:hi]).not_to respond_to(:coerced?)
130
- end
131
-
132
- it 'coerces values from a #replace call' do
133
- subject.coerce_value String, Coercable
134
-
135
- instance[:foo] = :bar
136
- instance.replace(foo: 'bar', bar: 'bax')
137
- expect(instance[:foo]).to be_coerced
138
- expect(instance[:bar]).to be_coerced
139
- end
140
-
141
- it 'does not coerce superclasses' do
142
- klass = Class.new(String)
143
- subject.coerce_value klass, Coercable
144
-
145
- instance[:foo] = 'bar'
146
- expect(instance[:foo]).not_to be_kind_of(Coercable)
147
- instance[:foo] = klass.new
148
- expect(instance[:foo]).to be_kind_of(Coercable)
149
- end
150
- end
151
- end
152
-
153
- after(:each) do
154
- Object.send(:remove_const, :ExampleCoercableHash)
155
- end
156
- end
@@ -1,70 +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
- location: {
15
- address: '123 Library St.'
16
- }
17
- }
18
- }
19
- end
20
- let(:instance) { subject.new.update(hash) }
21
-
22
- describe '#deep_fetch' do
23
- it 'extracts a value from a nested hash' do
24
- expect(instance.deep_fetch(:library, :location, :address)).to eq('123 Library St.')
25
- end
26
-
27
- it 'extracts a value from a nested array' do
28
- expect(instance.deep_fetch(:library, :books, 1, :title)).to eq('Moby Dick')
29
- end
30
-
31
- context 'when one of the keys is not present' do
32
- context 'when a block is provided' do
33
- it 'returns the value of the block' do
34
- value = instance.deep_fetch(:library, :unknown_key, :location) { 'block value' }
35
- expect(value).to eq('block value')
36
- end
37
- end
38
-
39
- context 'when a block is not provided' do
40
- context 'when the nested object is an array' do
41
- it 'raises an UndefinedPathError' do
42
- expect do
43
- instance.deep_fetch(:library, :books, 2)
44
- end.to(
45
- raise_error(
46
- DeepFetch::UndefinedPathError,
47
- 'Could not fetch path (library > books > 2) at 2'
48
- )
49
- )
50
- end
51
- end
52
-
53
- context 'when the nested object is a hash' do
54
- it 'raises a UndefinedPathError' do
55
- expect do
56
- instance.deep_fetch(:library, :location, :unknown_key)
57
- end.to(
58
- raise_error(
59
- DeepFetch::UndefinedPathError,
60
- 'Could not fetch path (library > location > unknown_key) at unknown_key'
61
- )
62
- )
63
- end
64
- end
65
- end
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,22 +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
- let(:h1) { subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } }) }
11
- let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } } } }
12
- let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } } } }
13
-
14
- it 'deep merges two hashes' do
15
- expect(h1.deep_merge(h2)).to eq expected_hash
16
- end
17
-
18
- it 'deep merges another hash in place via bang method' do
19
- h1.deep_merge!(h2)
20
- expect(h1).to eq expected_hash
21
- end
22
- end
@@ -1,23 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::IgnoreUndeclared do
4
- class ForgivingTrash < Hashie::Trash
5
- include Hashie::Extensions::IgnoreUndeclared
6
- property :city
7
- property :state, from: :provence
8
- end
9
-
10
- subject { ForgivingTrash }
11
-
12
- it 'silently ignores undeclared properties on initialization' do
13
- expect { subject.new(city: 'Toronto', provence: 'ON', country: 'Canada') }.to_not raise_error
14
- end
15
-
16
- it 'works with translated properties (with symbol keys)' do
17
- expect(subject.new(provence: 'Ontario').state).to eq('Ontario')
18
- end
19
-
20
- it 'works with translated properties (with string keys)' do
21
- expect(subject.new(provence: 'Ontario').state).to eq('Ontario')
22
- end
23
- end
@@ -1,152 +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
- shared_examples_for 'hash with indifferent access' do
30
- it 'is able to access via string or symbol' do
31
- h = subject.build(abc: 123)
32
- expect(h[:abc]).to eq 123
33
- expect(h['abc']).to eq 123
34
- end
35
-
36
- describe '#values_at' do
37
- it 'indifferently finds values' do
38
- h = subject.build(:foo => 'bar', 'baz' => 'qux')
39
- expect(h.values_at('foo', :baz)).to eq %w(bar qux)
40
- end
41
- end
42
-
43
- describe '#fetch' do
44
- it 'works like normal fetch, but indifferent' do
45
- h = subject.build(foo: 'bar')
46
- expect(h.fetch(:foo)).to eq h.fetch('foo')
47
- expect(h.fetch(:foo)).to eq 'bar'
48
- end
49
- end
50
-
51
- describe '#delete' do
52
- it 'deletes indifferently' do
53
- h = subject.build(:foo => 'bar', 'baz' => 'qux')
54
- h.delete('foo')
55
- h.delete(:baz)
56
- expect(h).to be_empty
57
- end
58
- end
59
-
60
- describe '#key?' do
61
- let(:h) { subject.build(foo: 'bar') }
62
-
63
- it 'finds it indifferently' do
64
- expect(h).to be_key(:foo)
65
- expect(h).to be_key('foo')
66
- end
67
-
68
- %w(include? member? has_key?).each do |key_alias|
69
- it "is aliased as #{key_alias}" do
70
- expect(h.send(key_alias.to_sym, :foo)).to be(true)
71
- expect(h.send(key_alias.to_sym, 'foo')).to be(true)
72
- end
73
- end
74
- end
75
-
76
- describe '#update' do
77
- let(:h) { subject.build(foo: 'bar') }
78
-
79
- it 'allows keys to be indifferent still' do
80
- h.update(baz: 'qux')
81
- expect(h['foo']).to eq 'bar'
82
- expect(h['baz']).to eq 'qux'
83
- end
84
-
85
- it 'recursively injects indifference into sub-hashes' do
86
- h.update(baz: { qux: 'abc' })
87
- expect(h['baz']['qux']).to eq 'abc'
88
- end
89
-
90
- it 'does not change the ancestors of the injected object class' do
91
- h.update(baz: { qux: 'abc' })
92
- expect(Hash.new).not_to be_respond_to(:indifferent_access?)
93
- end
94
- end
95
-
96
- describe '#replace' do
97
- let(:h) { subject.build(foo: 'bar').replace(bar: 'baz', hi: 'bye') }
98
-
99
- it 'returns self' do
100
- expect(h).to be_a(subject)
101
- end
102
-
103
- it 'removes old keys' do
104
- [:foo, 'foo'].each do |k|
105
- expect(h[k]).to be_nil
106
- expect(h.key?(k)).to be false
107
- end
108
- end
109
-
110
- it 'creates new keys with indifferent access' do
111
- [:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be true }
112
- expect(h[:bar]).to eq 'baz'
113
- expect(h['bar']).to eq 'baz'
114
- expect(h[:hi]).to eq 'bye'
115
- expect(h['hi']).to eq 'bye'
116
- end
117
- end
118
-
119
- describe '#try_convert' do
120
- describe 'with conversion' do
121
- let(:h) { subject.try_convert(foo: 'bar') }
122
-
123
- it 'is a subject' do
124
- expect(h).to be_a(subject)
125
- end
126
- end
127
-
128
- describe 'without conversion' do
129
- let(:h) { subject.try_convert('{ :foo => bar }') }
130
-
131
- it 'is nil' do
132
- expect(h).to be_nil
133
- end
134
- end
135
- end
136
- end
137
-
138
- describe 'with merge initializer' do
139
- subject { IndifferentHashWithMergeInitializer }
140
- it_should_behave_like 'hash with indifferent access'
141
- end
142
-
143
- describe 'with array initializer' do
144
- subject { IndifferentHashWithArrayInitializer }
145
- it_should_behave_like 'hash with indifferent access'
146
- end
147
-
148
- describe 'with try convert initializer' do
149
- subject { IndifferentHashWithTryConvertInitializer }
150
- it_should_behave_like 'hash with indifferent access'
151
- end
152
- end
@@ -1,103 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Extensions::KeyConversion do
4
- subject do
5
- klass = Class.new(::Hash)
6
- klass.send :include, Hashie::Extensions::KeyConversion
7
- klass
8
- end
9
-
10
- let(:instance) { subject.new }
11
-
12
- describe '#stringify_keys!' do
13
- it 'converts keys to strings' do
14
- instance[:abc] = 'abc'
15
- instance[123] = '123'
16
- instance.stringify_keys!
17
- expect((instance.keys & %w(abc 123)).size).to eq 2
18
- end
19
-
20
- it 'performs deep conversion within nested hashes' do
21
- instance[:ab] = subject.new
22
- instance[:ab][:cd] = subject.new
23
- instance[:ab][:cd][:ef] = 'abcdef'
24
- instance.stringify_keys!
25
- expect(instance).to eq('ab' => { 'cd' => { 'ef' => 'abcdef' } })
26
- end
27
-
28
- it 'performs deep conversion within nested arrays' do
29
- instance[:ab] = []
30
- instance[:ab] << subject.new
31
- instance[:ab] << subject.new
32
- instance[:ab][0][:cd] = 'abcd'
33
- instance[:ab][1][:ef] = 'abef'
34
- instance.stringify_keys!
35
- expect(instance).to eq('ab' => [{ 'cd' => 'abcd' }, { 'ef' => 'abef' }])
36
- end
37
-
38
- it 'returns itself' do
39
- expect(instance.stringify_keys!).to eq instance
40
- end
41
- end
42
-
43
- describe '#stringify_keys' do
44
- it 'converts keys to strings' do
45
- instance[:abc] = 'def'
46
- copy = instance.stringify_keys
47
- expect(copy['abc']).to eq 'def'
48
- end
49
-
50
- it 'does not alter the original' do
51
- instance[:abc] = 'def'
52
- copy = instance.stringify_keys
53
- expect(instance.keys).to eq [:abc]
54
- expect(copy.keys).to eq %w(abc)
55
- end
56
- end
57
-
58
- describe '#symbolize_keys!' do
59
- it 'converts keys to symbols' do
60
- instance['abc'] = 'abc'
61
- instance['def'] = 'def'
62
- instance.symbolize_keys!
63
- expect((instance.keys & [:abc, :def]).size).to eq 2
64
- end
65
-
66
- it 'performs deep conversion within nested hashes' do
67
- instance['ab'] = subject.new
68
- instance['ab']['cd'] = subject.new
69
- instance['ab']['cd']['ef'] = 'abcdef'
70
- instance.symbolize_keys!
71
- expect(instance).to eq(ab: { cd: { ef: 'abcdef' } })
72
- end
73
-
74
- it 'performs deep conversion within nested arrays' do
75
- instance['ab'] = []
76
- instance['ab'] << subject.new
77
- instance['ab'] << subject.new
78
- instance['ab'][0]['cd'] = 'abcd'
79
- instance['ab'][1]['ef'] = 'abef'
80
- instance.symbolize_keys!
81
- expect(instance).to eq(ab: [{ cd: 'abcd' }, { ef: 'abef' }])
82
- end
83
-
84
- it 'returns itself' do
85
- expect(instance.symbolize_keys!).to eq instance
86
- end
87
- end
88
-
89
- describe '#symbolize_keys' do
90
- it 'converts keys to symbols' do
91
- instance['abc'] = 'def'
92
- copy = instance.symbolize_keys
93
- expect(copy[:abc]).to eq 'def'
94
- end
95
-
96
- it 'does not alter the original' do
97
- instance['abc'] = 'def'
98
- copy = instance.symbolize_keys
99
- expect(instance.keys).to eq ['abc']
100
- expect(copy.keys).to eq [:abc]
101
- end
102
- end
103
- end