hashie 2.0.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +36 -0
  3. data/.travis.yml +13 -6
  4. data/CHANGELOG.md +40 -21
  5. data/CONTRIBUTING.md +110 -19
  6. data/Gemfile +9 -0
  7. data/LICENSE +1 -1
  8. data/README.md +347 -0
  9. data/Rakefile +4 -2
  10. data/hashie.gemspec +4 -7
  11. data/lib/hashie.rb +3 -0
  12. data/lib/hashie/clash.rb +19 -19
  13. data/lib/hashie/dash.rb +47 -39
  14. data/lib/hashie/extensions/coercion.rb +10 -6
  15. data/lib/hashie/extensions/deep_fetch.rb +29 -0
  16. data/lib/hashie/extensions/deep_merge.rb +15 -6
  17. data/lib/hashie/extensions/ignore_undeclared.rb +41 -0
  18. data/lib/hashie/extensions/indifferent_access.rb +37 -10
  19. data/lib/hashie/extensions/key_conversion.rb +3 -3
  20. data/lib/hashie/extensions/method_access.rb +9 -9
  21. data/lib/hashie/hash.rb +7 -7
  22. data/lib/hashie/hash_extensions.rb +5 -7
  23. data/lib/hashie/mash.rb +38 -31
  24. data/lib/hashie/rash.rb +119 -0
  25. data/lib/hashie/trash.rb +31 -22
  26. data/lib/hashie/version.rb +1 -1
  27. data/spec/hashie/clash_spec.rb +43 -45
  28. data/spec/hashie/dash_spec.rb +115 -53
  29. data/spec/hashie/extensions/coercion_spec.rb +42 -37
  30. data/spec/hashie/extensions/deep_fetch_spec.rb +70 -0
  31. data/spec/hashie/extensions/deep_merge_spec.rb +11 -9
  32. data/spec/hashie/extensions/ignore_undeclared_spec.rb +23 -0
  33. data/spec/hashie/extensions/indifferent_access_spec.rb +117 -64
  34. data/spec/hashie/extensions/key_conversion_spec.rb +28 -27
  35. data/spec/hashie/extensions/merge_initializer_spec.rb +13 -10
  36. data/spec/hashie/extensions/method_access_spec.rb +49 -40
  37. data/spec/hashie/hash_spec.rb +25 -13
  38. data/spec/hashie/mash_spec.rb +243 -187
  39. data/spec/hashie/rash_spec.rb +44 -0
  40. data/spec/hashie/trash_spec.rb +81 -43
  41. data/spec/hashie/version_spec.rb +7 -0
  42. data/spec/spec_helper.rb +0 -4
  43. metadata +27 -78
  44. data/.document +0 -5
  45. data/README.markdown +0 -236
  46. 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; include Hashie::Extensions::DeepMerge end
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(:a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } }) }
9
- let(:h2) { { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } }
10
- let(:expected_hash) { { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } }
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 'should deep merge two hashes' do
13
- h1.deep_merge(h2).should == expected_hash
14
+ it 'deep merges two hashes' do
15
+ h1.deep_merge(h2).should eq expected_hash
14
16
  end
15
17
 
16
- it 'should deep merge two hashes with bang method' do
18
+ it 'deep merges another hash in place via bang method' do
17
19
  h1.deep_merge!(h2)
18
- h1.should == expected_hash
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 IndifferentHash < Hash
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
- describe '#values_at' do
17
- it 'should indifferently find values' do
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
- describe '#fetch' do
24
- it 'should work like normal fetch, but indifferent' do
25
- h = subject.new(:foo => 'bar')
26
- h.fetch(:foo).should == h.fetch('foo')
27
- h.fetch(:foo).should == 'bar'
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
- describe '#delete' do
32
- it 'should delete indifferently' do
33
- h = subject.new(:foo => 'bar', 'baz' => 'qux')
34
- h.delete('foo')
35
- h.delete(:baz)
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
- describe '#key?' do
41
- let(:h) { subject.new(:foo => 'bar') }
42
-
43
- it 'should find it indifferently' do
44
- h.should be_key(:foo)
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
- %w(include? member? has_key?).each do |key_alias|
49
- it "should be aliased as #{key_alias}" do
50
- h.send(key_alias.to_sym, :foo).should be(true)
51
- h.send(key_alias.to_sym, 'foo').should be(true)
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
- describe '#update' do
57
- subject{ IndifferentHash.new(:foo => 'bar') }
58
- it 'should allow keys to be indifferent still' do
59
- subject.update(:baz => 'qux')
60
- subject['foo'].should == 'bar'
61
- subject['baz'].should == 'qux'
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
- it 'should recursively inject indifference into sub-hashes' do
65
- subject.update(:baz => {:qux => 'abc'})
66
- subject['baz']['qux'].should == 'abc'
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
- it 'should not change the ancestors of the injected object class' do
70
- subject.update(:baz => {:qux => 'abc'})
71
- Hash.new.should_not be_respond_to(:indifferent_access?)
72
- end
73
- end
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
- describe '#replace' do
76
- subject do
77
- IndifferentHash.new(:foo => 'bar').replace(:bar => 'baz', :hi => 'bye')
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
- it 'returns self' do
81
- subject.should be_a(IndifferentHash)
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
- it 'should remove old keys' do
85
- [:foo, 'foo'].each do |k|
86
- subject[k].should be_nil
87
- subject.key?(k).should be_false
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
- it 'creates new keys with indifferent access' do
92
- [:bar, 'bar', :hi, 'hi'].each { |k| subject.key?(k).should be_true }
93
- subject[:bar].should == 'baz'
94
- subject['bar'].should == 'baz'
95
- subject[:hi].should == 'bye'
96
- subject['hi'].should == 'bye'
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
- let(:instance){ subject.new }
9
+
10
+ let(:instance) { subject.new }
10
11
 
11
12
  describe '#stringify_keys!' do
12
- it 'should convert keys to strings' do
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 == 2
17
+ (instance.keys & %w(abc 123)).size.should eq 2
17
18
  end
18
19
 
19
- it 'should do deep conversion within nested hashes' do
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 == {'ab' => {'cd' => {'ef' => 'abcdef'}}}
25
+ instance.should eq('ab' => { 'cd' => { 'ef' => 'abcdef' } })
25
26
  end
26
27
 
27
- it 'should do deep conversion within nested arrays' do
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 == {'ab' => [{'cd' => 'abcd'}, {'ef' => 'abef'}]}
35
+ instance.should eq('ab' => [{ 'cd' => 'abcd' }, { 'ef' => 'abef' }])
35
36
  end
36
37
 
37
- it 'should return itself' do
38
- instance.stringify_keys!.should == instance
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 'should convert keys to strings' do
44
+ it 'converts keys to strings' do
44
45
  instance[:abc] = 'def'
45
46
  copy = instance.stringify_keys
46
- copy['abc'].should == 'def'
47
+ copy['abc'].should eq 'def'
47
48
  end
48
49
 
49
- it 'should not alter the original' do
50
+ it 'does not alter the original' do
50
51
  instance[:abc] = 'def'
51
52
  copy = instance.stringify_keys
52
- instance.keys.should == [:abc]
53
- copy.keys.should == %w(abc)
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 'should convert keys to symbols' do
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 == 2
63
+ (instance.keys & [:abc, :def]).size.should eq 2
63
64
  end
64
65
 
65
- it 'should do deep conversion within nested hashes' do
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 == {:ab => {:cd => {:ef => 'abcdef'}}}
71
+ instance.should eq(ab: { cd: { ef: 'abcdef' } })
71
72
  end
72
73
 
73
- it 'should do deep conversion within nested arrays' do
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 == {:ab => [{:cd => 'abcd'}, {:ef => 'abef'}]}
81
+ instance.should eq(ab: [{ cd: 'abcd' }, { ef: 'abef' }])
81
82
  end
82
83
 
83
- it 'should return itself' do
84
- instance.symbolize_keys!.should == instance
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 'should convert keys to symbols' do
90
+ it 'converts keys to symbols' do
90
91
  instance['abc'] = 'def'
91
92
  copy = instance.symbolize_keys
92
- copy[:abc].should == 'def'
93
+ copy[:abc].should eq 'def'
93
94
  end
94
95
 
95
- it 'should not alter the original' do
96
+ it 'does not alter the original' do
96
97
  instance['abc'] = 'def'
97
98
  copy = instance.symbolize_keys
98
- instance.keys.should == ['abc']
99
- copy.keys.should == [:abc]
99
+ instance.keys.should eq ['abc']
100
+ copy.keys.should eq [:abc]
100
101
  end
101
102
  end
102
103
  end