dasil003-safe-nested-hash 0.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.
- data/LICENSE +20 -0
- data/README.md +40 -0
- data/VERSION.yml +4 -0
- data/lib/safe_nested_hash.rb +43 -0
- data/test/safe_nested_hash_test.rb +58 -0
- data/test/test_helper.rb +10 -0
- metadata +60 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Gabe da Silveira
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# SafeNestedHash
|
2
|
+
|
3
|
+
Allows you to populate nested hashes without initializing every level first:
|
4
|
+
|
5
|
+
h = SafeNestedHash.new
|
6
|
+
h[1][2][3] = :foo
|
7
|
+
h
|
8
|
+
=> {1 => {2 => {3 => :foo}}}
|
9
|
+
|
10
|
+
Don't be tempted by more concise implementations such as can be found at http://www.ruby-forum.com/topic/111524. The fundamental challenge to overcome here is that accessing a non-existent key should not alter the original hash, yet setting a deeply nested hash needs to attach to the original hash (potentially) several levels up. This is accomplished by means of a custom UndefinedHash class which tracks lookup path and reconstructs it if an assignment is found at the end:
|
11
|
+
|
12
|
+
h = SafeNestedHash.new
|
13
|
+
h[1]
|
14
|
+
=> #<SafeNestedHash::UndefinedHash:0x473d648 @first_key=1, @base={}, @keychain=[]>
|
15
|
+
|
16
|
+
h = SafeNestedHash.new
|
17
|
+
h[1][2][3]
|
18
|
+
=> #<SafeNestedHash::UndefinedHash:0x473bca8 @first_key=1, @base={}, @keychain=[2, 3]>
|
19
|
+
|
20
|
+
h = SafeNestedHash.new
|
21
|
+
h[1][2][3] = :foo
|
22
|
+
h[1][2][4][5][6]
|
23
|
+
=> #<SafeNestedHash::UndefinedHash:0x4737464 @first_key=4, @base={3=>:foo}, @keychain=[5, 6]>
|
24
|
+
h
|
25
|
+
=> {1=>{2=>{3=>:foo}}}
|
26
|
+
|
27
|
+
To check for existence of a key you must use nil?. Smelly I know, and you can't trivially differentiate from an actual nil stored in the hash, but look Ma, no monkey patching:
|
28
|
+
|
29
|
+
h = SafeNestedHash.new
|
30
|
+
h[1][2][3] = :foo
|
31
|
+
h[1][2][3].nil?
|
32
|
+
=> false
|
33
|
+
h[2].nil?
|
34
|
+
=> true
|
35
|
+
h[2][3].nil?
|
36
|
+
=> true
|
37
|
+
|
38
|
+
## Copyright
|
39
|
+
|
40
|
+
Copyright (c) 2009 Gabe da Silveira. See LICENSE for details.
|
data/VERSION.yml
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class SafeNestedHash < Hash
|
2
|
+
def [](key)
|
3
|
+
if has_key?(key)
|
4
|
+
super
|
5
|
+
else
|
6
|
+
UndefinedHash.new(self, key)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UndefinedHash #:nodoc:
|
11
|
+
def initialize(base, key)
|
12
|
+
@base = base
|
13
|
+
@first_key = key
|
14
|
+
@keychain = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def nil?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@keychain << key
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
@keychain << key
|
28
|
+
@base[@first_key] = build_chain(@keychain, value)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def build_chain(remaining, value)
|
33
|
+
if remaining.empty?
|
34
|
+
value
|
35
|
+
else
|
36
|
+
key = remaining.shift
|
37
|
+
hash = SafeNestedHash.new
|
38
|
+
hash[key] = build_chain(remaining, value)
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SafeNestedHashTest < Test::Unit::TestCase
|
4
|
+
context "empty safe_nested_hash" do
|
5
|
+
setup do
|
6
|
+
@hash = SafeNestedHash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "return an undefined hash for key" do
|
10
|
+
assert_equal SafeNestedHash::UndefinedHash, @hash[1].class
|
11
|
+
end
|
12
|
+
|
13
|
+
should "return an undefined_hash for nested key" do
|
14
|
+
assert_equal SafeNestedHash::UndefinedHash, @hash[1][2].class
|
15
|
+
end
|
16
|
+
|
17
|
+
should "define a key" do
|
18
|
+
@hash[1] = 'a'
|
19
|
+
assert_equal 'a', @hash[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
should "define a nested key" do
|
23
|
+
@hash[1][2][3] = 'c'
|
24
|
+
assert_equal 'c', @hash[1][2][3]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "nested safe_nested_hash" do
|
29
|
+
setup do
|
30
|
+
@hash = SafeNestedHash.new
|
31
|
+
@hash[1][2][3] = 'a'
|
32
|
+
@hash[2] = 'b'
|
33
|
+
end
|
34
|
+
|
35
|
+
should "return an undefined hash for new nested key" do
|
36
|
+
assert_equal SafeNestedHash::UndefinedHash, @hash[3][4].class
|
37
|
+
end
|
38
|
+
|
39
|
+
should "return an undefined hash for partially defined nested key" do
|
40
|
+
assert_equal SafeNestedHash::UndefinedHash, @hash[1][4].class
|
41
|
+
end
|
42
|
+
|
43
|
+
should "return an undefined hash which returns true for nil?" do
|
44
|
+
assert @hash[1][4].nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
should "not clobber existing value when adding to a partial chain" do
|
48
|
+
@hash[1][2][4][5] = 'c'
|
49
|
+
assert_equal 'c', @hash[1][2][4][5]
|
50
|
+
end
|
51
|
+
|
52
|
+
should "raise error if attempting to extend non-hash" do
|
53
|
+
assert_raises IndexError do
|
54
|
+
@hash[2][3] = 'd'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dasil003-safe-nested-hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabe da Silveira
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-01 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: gabe@websaviour.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.md
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- README.md
|
27
|
+
- VERSION.yml
|
28
|
+
- lib/safe_nested_hash.rb
|
29
|
+
- test/safe_nested_hash_test.rb
|
30
|
+
- test/test_helper.rb
|
31
|
+
- LICENSE
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/dasil003/safe-nested-hash
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --inline-source
|
37
|
+
- --charset=UTF-8
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
requirements: []
|
53
|
+
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.2.0
|
56
|
+
signing_key:
|
57
|
+
specification_version: 2
|
58
|
+
summary: Populate deep nested hashes without initialization situps
|
59
|
+
test_files: []
|
60
|
+
|