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.
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