hashie 2.0.5 → 2.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +13 -6
- data/CHANGELOG.md +40 -21
- data/CONTRIBUTING.md +110 -19
- data/Gemfile +9 -0
- data/LICENSE +1 -1
- data/README.md +347 -0
- data/Rakefile +4 -2
- data/hashie.gemspec +4 -7
- data/lib/hashie.rb +3 -0
- data/lib/hashie/clash.rb +19 -19
- data/lib/hashie/dash.rb +47 -39
- data/lib/hashie/extensions/coercion.rb +10 -6
- data/lib/hashie/extensions/deep_fetch.rb +29 -0
- data/lib/hashie/extensions/deep_merge.rb +15 -6
- data/lib/hashie/extensions/ignore_undeclared.rb +41 -0
- data/lib/hashie/extensions/indifferent_access.rb +37 -10
- data/lib/hashie/extensions/key_conversion.rb +3 -3
- data/lib/hashie/extensions/method_access.rb +9 -9
- data/lib/hashie/hash.rb +7 -7
- data/lib/hashie/hash_extensions.rb +5 -7
- data/lib/hashie/mash.rb +38 -31
- data/lib/hashie/rash.rb +119 -0
- data/lib/hashie/trash.rb +31 -22
- data/lib/hashie/version.rb +1 -1
- data/spec/hashie/clash_spec.rb +43 -45
- data/spec/hashie/dash_spec.rb +115 -53
- data/spec/hashie/extensions/coercion_spec.rb +42 -37
- data/spec/hashie/extensions/deep_fetch_spec.rb +70 -0
- data/spec/hashie/extensions/deep_merge_spec.rb +11 -9
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +23 -0
- data/spec/hashie/extensions/indifferent_access_spec.rb +117 -64
- data/spec/hashie/extensions/key_conversion_spec.rb +28 -27
- data/spec/hashie/extensions/merge_initializer_spec.rb +13 -10
- data/spec/hashie/extensions/method_access_spec.rb +49 -40
- data/spec/hashie/hash_spec.rb +25 -13
- data/spec/hashie/mash_spec.rb +243 -187
- data/spec/hashie/rash_spec.rb +44 -0
- data/spec/hashie/trash_spec.rb +81 -43
- data/spec/hashie/version_spec.rb +7 -0
- data/spec/spec_helper.rb +0 -4
- metadata +27 -78
- data/.document +0 -5
- data/README.markdown +0 -236
- data/lib/hashie/extensions/structure.rb +0 -47
@@ -0,0 +1,70 @@
|
|
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
|
+
instance.deep_fetch(:library, :location, :address).should eq('123 Library St.')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'extracts a value from a nested array' do
|
28
|
+
instance.deep_fetch(:library, :books, 1, :title).should 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
|
+
value.should 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
|
+
lambda do
|
43
|
+
instance.deep_fetch(:library, :books, 2)
|
44
|
+
end.should(
|
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
|
+
lambda do
|
56
|
+
instance.deep_fetch(:library, :location, :unknown_key)
|
57
|
+
end.should(
|
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,20 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Hashie::Extensions::DeepMerge do
|
4
|
-
class DeepMergeHash < Hash
|
4
|
+
class DeepMergeHash < Hash
|
5
|
+
include Hashie::Extensions::DeepMerge
|
6
|
+
end
|
5
7
|
|
6
|
-
subject{ DeepMergeHash }
|
8
|
+
subject { DeepMergeHash }
|
7
9
|
|
8
|
-
let(:h1) { subject.new.merge(:
|
9
|
-
let(:h2) { { :
|
10
|
-
let(:expected_hash) { { :
|
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' } } } }
|
11
13
|
|
12
|
-
it '
|
13
|
-
h1.deep_merge(h2).should
|
14
|
+
it 'deep merges two hashes' do
|
15
|
+
h1.deep_merge(h2).should eq expected_hash
|
14
16
|
end
|
15
17
|
|
16
|
-
it '
|
18
|
+
it 'deep merges another hash in place via bang method' do
|
17
19
|
h1.deep_merge!(h2)
|
18
|
-
h1.should
|
20
|
+
h1.should eq expected_hash
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,23 @@
|
|
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,99 +1,152 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Hashie::Extensions::IndifferentAccess do
|
4
|
-
class
|
4
|
+
class IndifferentHashWithMergeInitializer < Hash
|
5
5
|
include Hashie::Extensions::MergeInitializer
|
6
6
|
include Hashie::Extensions::IndifferentAccess
|
7
|
-
end
|
8
|
-
subject{ IndifferentHash }
|
9
|
-
|
10
|
-
it 'should be able to access via string or symbol' do
|
11
|
-
h = subject.new(:abc => 123)
|
12
|
-
h[:abc].should == 123
|
13
|
-
h['abc'].should == 123
|
14
|
-
end
|
15
7
|
|
16
|
-
|
17
|
-
|
18
|
-
h = subject.new(:foo => 'bar', 'baz' => 'qux')
|
19
|
-
h.values_at('foo', :baz).should == %w(bar qux)
|
8
|
+
class << self
|
9
|
+
alias_method :build, :new
|
20
10
|
end
|
21
11
|
end
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
13
|
+
class IndifferentHashWithArrayInitializer < Hash
|
14
|
+
include Hashie::Extensions::IndifferentAccess
|
15
|
+
|
16
|
+
class << self
|
17
|
+
alias_method :build, :[]
|
28
18
|
end
|
29
19
|
end
|
30
20
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
h.should be_empty
|
21
|
+
class IndifferentHashWithTryConvertInitializer < Hash
|
22
|
+
include Hashie::Extensions::IndifferentAccess
|
23
|
+
|
24
|
+
class << self
|
25
|
+
alias_method :build, :try_convert
|
37
26
|
end
|
38
27
|
end
|
39
28
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
h.should
|
45
|
-
h.should be_key('foo')
|
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
|
+
h[:abc].should eq 123
|
33
|
+
h['abc'].should eq 123
|
46
34
|
end
|
47
35
|
|
48
|
-
|
49
|
-
it
|
50
|
-
h.
|
51
|
-
h.
|
36
|
+
describe '#values_at' do
|
37
|
+
it 'indifferently finds values' do
|
38
|
+
h = subject.build(:foo => 'bar', 'baz' => 'qux')
|
39
|
+
h.values_at('foo', :baz).should eq %w(bar qux)
|
52
40
|
end
|
53
41
|
end
|
54
|
-
end
|
55
42
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
43
|
+
describe '#fetch' do
|
44
|
+
it 'works like normal fetch, but indifferent' do
|
45
|
+
h = subject.build(foo: 'bar')
|
46
|
+
h.fetch(:foo).should eq h.fetch('foo')
|
47
|
+
h.fetch(:foo).should eq 'bar'
|
48
|
+
end
|
62
49
|
end
|
63
50
|
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
+
h.should be_empty
|
57
|
+
end
|
67
58
|
end
|
68
59
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
60
|
+
describe '#key?' do
|
61
|
+
let(:h) { subject.build(foo: 'bar') }
|
62
|
+
|
63
|
+
it 'finds it indifferently' do
|
64
|
+
h.should be_key(:foo)
|
65
|
+
h.should be_key('foo')
|
66
|
+
end
|
74
67
|
|
75
|
-
|
76
|
-
|
77
|
-
|
68
|
+
%w(include? member? has_key?).each do |key_alias|
|
69
|
+
it "is aliased as #{key_alias}" do
|
70
|
+
h.send(key_alias.to_sym, :foo).should be(true)
|
71
|
+
h.send(key_alias.to_sym, 'foo').should be(true)
|
72
|
+
end
|
73
|
+
end
|
78
74
|
end
|
79
75
|
|
80
|
-
|
81
|
-
subject.
|
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
|
+
h['foo'].should eq 'bar'
|
82
|
+
h['baz'].should eq 'qux'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'recursively injects indifference into sub-hashes' do
|
86
|
+
h.update(baz: { qux: 'abc' })
|
87
|
+
h['baz']['qux'].should 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
|
+
Hash.new.should_not be_respond_to(:indifferent_access?)
|
93
|
+
end
|
82
94
|
end
|
83
95
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
describe '#replace' do
|
97
|
+
let(:h) { subject.build(foo: 'bar').replace(bar: 'baz', hi: 'bye') }
|
98
|
+
|
99
|
+
it 'returns self' do
|
100
|
+
h.should be_a(subject)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'removes old keys' do
|
104
|
+
[:foo, 'foo'].each do |k|
|
105
|
+
h[k].should be_nil
|
106
|
+
h.key?(k).should be_false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'creates new keys with indifferent access' do
|
111
|
+
[:bar, 'bar', :hi, 'hi'].each { |k| h.key?(k).should be_true }
|
112
|
+
h[:bar].should eq 'baz'
|
113
|
+
h['bar'].should eq 'baz'
|
114
|
+
h[:hi].should eq 'bye'
|
115
|
+
h['hi'].should eq 'bye'
|
88
116
|
end
|
89
117
|
end
|
90
118
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
+
h.should 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
|
+
h.should be_nil
|
133
|
+
end
|
134
|
+
end
|
97
135
|
end
|
98
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
|
99
152
|
end
|
@@ -6,97 +6,98 @@ describe Hashie::Extensions::KeyConversion do
|
|
6
6
|
klass.send :include, Hashie::Extensions::KeyConversion
|
7
7
|
klass
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
|
+
let(:instance) { subject.new }
|
10
11
|
|
11
12
|
describe '#stringify_keys!' do
|
12
|
-
it '
|
13
|
+
it 'converts keys to strings' do
|
13
14
|
instance[:abc] = 'abc'
|
14
15
|
instance[123] = '123'
|
15
16
|
instance.stringify_keys!
|
16
|
-
(instance.keys & %w(abc 123)).size.should
|
17
|
+
(instance.keys & %w(abc 123)).size.should eq 2
|
17
18
|
end
|
18
19
|
|
19
|
-
it '
|
20
|
+
it 'performs deep conversion within nested hashes' do
|
20
21
|
instance[:ab] = subject.new
|
21
22
|
instance[:ab][:cd] = subject.new
|
22
23
|
instance[:ab][:cd][:ef] = 'abcdef'
|
23
24
|
instance.stringify_keys!
|
24
|
-
instance.should
|
25
|
+
instance.should eq('ab' => { 'cd' => { 'ef' => 'abcdef' } })
|
25
26
|
end
|
26
27
|
|
27
|
-
it '
|
28
|
+
it 'performs deep conversion within nested arrays' do
|
28
29
|
instance[:ab] = []
|
29
30
|
instance[:ab] << subject.new
|
30
31
|
instance[:ab] << subject.new
|
31
32
|
instance[:ab][0][:cd] = 'abcd'
|
32
33
|
instance[:ab][1][:ef] = 'abef'
|
33
34
|
instance.stringify_keys!
|
34
|
-
instance.should
|
35
|
+
instance.should eq('ab' => [{ 'cd' => 'abcd' }, { 'ef' => 'abef' }])
|
35
36
|
end
|
36
37
|
|
37
|
-
it '
|
38
|
-
instance.stringify_keys!.should
|
38
|
+
it 'returns itself' do
|
39
|
+
instance.stringify_keys!.should eq instance
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
describe '#stringify_keys' do
|
43
|
-
it '
|
44
|
+
it 'converts keys to strings' do
|
44
45
|
instance[:abc] = 'def'
|
45
46
|
copy = instance.stringify_keys
|
46
|
-
copy['abc'].should
|
47
|
+
copy['abc'].should eq 'def'
|
47
48
|
end
|
48
49
|
|
49
|
-
it '
|
50
|
+
it 'does not alter the original' do
|
50
51
|
instance[:abc] = 'def'
|
51
52
|
copy = instance.stringify_keys
|
52
|
-
instance.keys.should
|
53
|
-
copy.keys.should
|
53
|
+
instance.keys.should eq [:abc]
|
54
|
+
copy.keys.should eq %w(abc)
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
57
58
|
describe '#symbolize_keys!' do
|
58
|
-
it '
|
59
|
+
it 'converts keys to symbols' do
|
59
60
|
instance['abc'] = 'abc'
|
60
61
|
instance['def'] = 'def'
|
61
62
|
instance.symbolize_keys!
|
62
|
-
(instance.keys & [:abc, :def]).size.should
|
63
|
+
(instance.keys & [:abc, :def]).size.should eq 2
|
63
64
|
end
|
64
65
|
|
65
|
-
it '
|
66
|
+
it 'performs deep conversion within nested hashes' do
|
66
67
|
instance['ab'] = subject.new
|
67
68
|
instance['ab']['cd'] = subject.new
|
68
69
|
instance['ab']['cd']['ef'] = 'abcdef'
|
69
70
|
instance.symbolize_keys!
|
70
|
-
instance.should
|
71
|
+
instance.should eq(ab: { cd: { ef: 'abcdef' } })
|
71
72
|
end
|
72
73
|
|
73
|
-
it '
|
74
|
+
it 'performs deep conversion within nested arrays' do
|
74
75
|
instance['ab'] = []
|
75
76
|
instance['ab'] << subject.new
|
76
77
|
instance['ab'] << subject.new
|
77
78
|
instance['ab'][0]['cd'] = 'abcd'
|
78
79
|
instance['ab'][1]['ef'] = 'abef'
|
79
80
|
instance.symbolize_keys!
|
80
|
-
instance.should
|
81
|
+
instance.should eq(ab: [{ cd: 'abcd' }, { ef: 'abef' }])
|
81
82
|
end
|
82
83
|
|
83
|
-
it '
|
84
|
-
instance.symbolize_keys!.should
|
84
|
+
it 'returns itself' do
|
85
|
+
instance.symbolize_keys!.should eq instance
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
89
|
describe '#symbolize_keys' do
|
89
|
-
it '
|
90
|
+
it 'converts keys to symbols' do
|
90
91
|
instance['abc'] = 'def'
|
91
92
|
copy = instance.symbolize_keys
|
92
|
-
copy[:abc].should
|
93
|
+
copy[:abc].should eq 'def'
|
93
94
|
end
|
94
95
|
|
95
|
-
it '
|
96
|
+
it 'does not alter the original' do
|
96
97
|
instance['abc'] = 'def'
|
97
98
|
copy = instance.symbolize_keys
|
98
|
-
instance.keys.should
|
99
|
-
copy.keys.should
|
99
|
+
instance.keys.should eq ['abc']
|
100
|
+
copy.keys.should eq [:abc]
|
100
101
|
end
|
101
102
|
end
|
102
103
|
end
|