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 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.
@@ -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.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'safe_nested_hash'
8
+
9
+ class Test::Unit::TestCase
10
+ end
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
+