hashery 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ = OpenHash
2
+
3
+ An OpenHash is a Hash that provides +open+ access to its entries via method
4
+ calls. Writers (methods ending in =-marks) assign entries. Methods without
5
+ special puncuation will retrieve entries.
6
+
7
+ require 'hashery/openhash'
8
+
9
+ o = OpenHash.new
10
+ o.a = 1
11
+ o.b = 2
12
+ o.a.assert == 1
13
+ o.b.assert == 2
14
+
15
+ Writers always use a Symbol for keys in the underlying Hash.
16
+
17
+ o.to_h.assert == { :a=>1, :b=>2 }
18
+
19
+ All the usual Hash methods are still available in an OpenHash.
20
+
21
+ o.map{ |k,v| [k,v] }
22
+
23
+ And they are protected from being overridden by writers.
24
+
25
+ o.map = 3
26
+ o.map{ |k,v| [k,v] }
27
+
28
+ Even so, the underlying Hash object does contain the entry even
29
+ when it can not be accessed via a reader method.
30
+
31
+ o.to_h.assert == {:a=>1, :b=>2, :map=>3 }
32
+
33
+ For some usecases it may be necessary to give up access to one or
34
+ more Hash methods in favor of access to the hash entries. This can
35
+ be done using the #omit! method.
36
+
37
+ o.omit!(:map, :merge)
38
+ o.map.assert == 3
39
+ o.merge = 4
40
+ o.merge.assert == 4
41
+
42
+ Becuase of nature of a writer, a certain set of Hash methods are always
43
+ protected, namely any of those ending in a puctuation mark. In addition,
44
+ the implementation protects all shadow methods (e.g. __id__).
45
+
46
+ o.__id__ = 4
47
+ o.__id__.assert! == 4
48
+
49
+ Even though writers alwasy use Symbols as keys, because an OpenHash
50
+ is a true Hash object, any object can be used as a key internally.
51
+
52
+ o = OpenHash.new
53
+ o[nil] = "Nothing"
54
+ o.to_h.assert == { nil=>"Nothing" }
55
+
56
+ It simply will not be accessible via a reader method.
57
+
@@ -0,0 +1,21 @@
1
+ = QueryHash
2
+
3
+ A QueryHash is a Hash that provides open access much like
4
+ an OpenHash, but it limits readers to query methods (i.e.
5
+ method ending in ?-marks).
6
+
7
+ require 'hashery/queryhash'
8
+
9
+ q = QueryHash.new
10
+ q.a = 1
11
+ q.b = 2
12
+ q.a?.assert == 1
13
+ q.b?.assert == 2
14
+
15
+ Writers always use Symbols for entry keys.
16
+
17
+ q.assert == { :a=>1, :b=>2 }
18
+
19
+ A QueryHash is compatible with Ruby's standard Hash in
20
+ every other respect.
21
+
@@ -0,0 +1,13 @@
1
+ = CastingHash
2
+
3
+ A CastingHash is a Hash that allows _casting_ procedures to
4
+ defined that the keys and values pass through upon assignment.
5
+
6
+ require 'hashery/castinghash'
7
+
8
+ c = CastingHash.new{ |x| x.to_s }
9
+
10
+ c[:a] = 1
11
+
12
+ c.assert == {'a'=>1}
13
+
@@ -0,0 +1,22 @@
1
+ = StaticHash
2
+
3
+ A StaticHash is simply a Hash that can only be assigned
4
+ once per key. Once assigned a subsequent attempt to assign
5
+ a value to the same key will raise an ArgumentError.
6
+
7
+ require 'hashery/statichash'
8
+
9
+ h = StaticHash.new
10
+
11
+ h["x"] = 1
12
+
13
+ expect ArgumentError do
14
+ h["x"] = 2
15
+ end
16
+
17
+ The same error will be raised when using #update or #merge!.
18
+
19
+ expect ArgumentError do
20
+ h.update( :x=>2 )
21
+ end
22
+
@@ -0,0 +1,59 @@
1
+ = Association
2
+
3
+ An Association is a class for creating simple pairings.
4
+
5
+ require 'hashery/association'
6
+
7
+ An Association can bew created through the usual means
8
+ of instantiation.
9
+
10
+ Association.new(:a, :b)
11
+
12
+ Or the shortcut method #>> can be used in most cases.
13
+
14
+ :x >> :z
15
+
16
+ An association provides two methods to access its content, #index and #value.
17
+
18
+ a = 'foo' >> 'bar'
19
+
20
+ a.index.assert == 'foo'
21
+ a.value.assert == 'bar'
22
+
23
+ Associations can be used to create ordered-hashes via normal
24
+ arrays.
25
+
26
+ keys = []
27
+ vals = []
28
+
29
+ ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
30
+
31
+ ohash.each{ |k,v| keys << k ; vals << v }
32
+
33
+ keys.assert == ['A','B','C']
34
+ vals.assert == ['3','2','1']
35
+
36
+
37
+ Becuase Associations are objects in themselves more complex
38
+ collections can also be created.
39
+
40
+ complex = [
41
+ 'parent' >> 'child',
42
+ 'childless',
43
+ 'another_parent' >> [
44
+ 'subchildless',
45
+ 'subparent' >> 'subchild'
46
+ ]
47
+ ]
48
+
49
+ An experimental feature of Association keeps a cache of all defined associations.
50
+
51
+ o = Object.new
52
+ o >> :a
53
+ o >> :b
54
+ o >> :c
55
+
56
+ o.associations.assert == [:a, :b, :c]
57
+
58
+ However this feature will probably be deprecated.
59
+
@@ -0,0 +1,58 @@
1
+ = OpenCascade
2
+
3
+ OpenCascade is subclass of OpenObject. It differs in a few
4
+ significant ways.
5
+
6
+ require 'hashery/opencascade'
7
+
8
+ The main reason this class is labeled "cascade", every internal
9
+ Hash is transformed into an OpenCascade dynamically upon access.
10
+ This makes it easy to create "cascading" references.
11
+
12
+ h = { :x => { :y => { :z => 1 } } }
13
+ c = OpenCascade[h]
14
+ c.x.y.z.assert == 1
15
+
16
+ As soon as you access a node it automatically becomes an OpenCascade.
17
+
18
+ c = OpenCascade.new
19
+ c.r.assert.is_a? OpenCascade
20
+ c.a.b.assert.is_a? OpenCascade
21
+
22
+ But if you set a node, then that will be it's value.
23
+
24
+ c.a.b = 4
25
+ c.a.b.assert == 4
26
+
27
+ To query a node without causing the auto-creation of an OpenCasade
28
+ object, use the ?-mark.
29
+
30
+ c.a.z?.assert == nil
31
+
32
+ OpenCascade also transforms Hashes within Arrays.
33
+
34
+ h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 }
35
+ c = OpenCascade[h]
36
+ c.x.first.a.assert == 1
37
+ c.x.last.a.assert == 2
38
+
39
+ Like OpenObject, OpenCascade allows you to insert entries as array
40
+ pairs.
41
+
42
+ c = OpenCascade.new
43
+ c << [:x,8]
44
+ c << [:y,9]
45
+
46
+ c.x.assert == 8
47
+ c.y.assert == 9
48
+
49
+ Finally, you can call methods ending in a !-mark to access the
50
+ underlying hash (Note that these differ in behavior from the
51
+ built-in !-methods).
52
+
53
+ bk = c.map!{ |k,v| k.to_s.upcase }
54
+ bk.sort.assert == ['X', 'Y']
55
+
56
+ So you can see that for the most an OpenCascade is just like
57
+ OpenObject, but it allows you to conveniently build open sub-layers.
58
+
@@ -0,0 +1,141 @@
1
+ = FuzzyHash
2
+
3
+ Require the library.
4
+
5
+ require 'hashery/fuzzyhash'
6
+
7
+ Should accept strings and retrieve based on them.
8
+
9
+ l = FuzzyHash.new
10
+ l['asd'] = 'qwe'
11
+ l['asd'].should == 'qwe'
12
+
13
+ Should accept strings, but the second time you set the same string, it should overwrite.
14
+
15
+ l = FuzzyHash.new
16
+ l['asd'] = 'asd'
17
+ l['asd'] = 'qwe'
18
+ l['asd'].should == 'qwe'
19
+
20
+ Should accept regexs too.
21
+
22
+ l = FuzzyHash.new
23
+ l[/asd.*/] = 'qwe'
24
+ l['asdqweasd'].should == 'qwe'
25
+
26
+ Should accept regexs too, but the second time you set the same regex, it should overwrite.
27
+
28
+ l = FuzzyHash.new
29
+ l[/asd/] = 'asd'
30
+ l[/asd/] = 'qwe'
31
+ l['asdqweasd'].should == 'qwe'
32
+
33
+ Should accept regexs too with the match.
34
+
35
+ l = FuzzyHash.new
36
+ l[/asd.*/] = 'qwe'
37
+ l.match_with_result('asdqweasd').should == ['qwe', 'asdqweasd']
38
+
39
+ Should accept regexs that match the whole strong too with the match.
40
+
41
+ l = FuzzyHash.new
42
+ l[/asd/] = 'qwe'
43
+ l.match_with_result('asd').should == ['qwe', 'asd']
44
+
45
+ Should prefer string to regex matches.
46
+
47
+ l = FuzzyHash.new
48
+ l['asd'] = 'qwe2'
49
+ l[/asd.*/] = 'qwe'
50
+ l['asd'].should == 'qwe2'
51
+
52
+ Should allow nil keys.
53
+
54
+ l = FuzzyHash.new
55
+ l[nil] = 'qwe2'
56
+ l['asd'] = 'qwe'
57
+ l['asd'].should == 'qwe'
58
+ l[nil].should == 'qwe2'
59
+
60
+ Should allow boolean keys.
61
+
62
+ l = FuzzyHash.new
63
+ l[false] = 'false'
64
+ l[true] = 'true'
65
+ l[/.*/] = 'everything else'
66
+ l[true].should == 'true'
67
+ l[false].should == 'false'
68
+ l['false'].should == 'everything else'
69
+
70
+ Should pick between the correct regex.
71
+
72
+ hash = FuzzyHash.new
73
+ hash[/^\d+$/] = 'number'
74
+ hash[/.*/] = 'something'
75
+ hash['123asd'].should == 'something'
76
+
77
+ Should be able to delete by value for hash.
78
+
79
+ l = FuzzyHash.new
80
+ l[nil] = 'qwe2'
81
+ l['asd'] = 'qwe'
82
+ l['asd'].should == 'qwe'
83
+ l[nil].should == 'qwe2'
84
+ l.delete_value('qwe2')
85
+ l[nil].should == nil
86
+
87
+ Should be able to delete by value for regex.
88
+
89
+ l = FuzzyHash.new
90
+ l[/qwe.*/] = 'qwe2'
91
+ l['asd'] = 'qwe'
92
+ l['asd'].should == 'qwe'
93
+ l['qweasd'].should == 'qwe2'
94
+ l.delete_value('qwe2')
95
+ l['qweasd'].should == nil
96
+
97
+ Should iterate through the keys.
98
+
99
+ l = FuzzyHash.new
100
+ l[/qwe.*/] = 'qwe2'
101
+ l['asd'] = 'qwe'
102
+ l['zxc'] = 'qwe'
103
+ l.keys.size.should == 3
104
+
105
+ Should iterate through the values.
106
+
107
+ l = FuzzyHash.new
108
+ l[/qwe.*/] = 'qwe2'
109
+ l['asd'] = 'qwe'
110
+ l['zxc'] = 'qwelkj'
111
+ (['qwe2','qwe','qwelkj'] & l.values).size.should == 3
112
+
113
+ Should clear.
114
+
115
+ l = FuzzyHash.new
116
+ l[/qwe.*/] = 'qwe2'
117
+ l['asd'] = 'qwe'
118
+ l['zxc'] = 'qwelkj'
119
+ l.clear
120
+ l.empty?.should == true
121
+
122
+ Should handle equality.
123
+
124
+ l_1 = FuzzyHash.new
125
+ l_1[/qwe.*/] = 'qwe2'
126
+ l_1['asd'] = 'qwelkj'
127
+ l_1['zxc'] = 'qwe'
128
+ l_2 = FuzzyHash.new
129
+ l_2['zxc'] = 'qwe'
130
+ l_2['asd'] = 'qwelkj'
131
+ l_2[/qwe.*/] = 'qwe2'
132
+ l_1.should == l_2
133
+
134
+ Should return the value when adding the value.
135
+
136
+ h = FuzzyHash.new
137
+ (h[/asd/] = '123').should == '123'
138
+ (h['qwe'] = '123').should == '123'
139
+
140
+ That's It.
141
+
@@ -0,0 +1,38 @@
1
+ = PropertyHash
2
+
3
+ Require the library.
4
+
5
+ require 'hashery/propertyhash'
6
+
7
+ The Property hash can be used an object in itself.
8
+
9
+ h = PropertyHash.new(:a=>1, :b=>2)
10
+ h[:a] #=> 1
11
+ h[:a] = 3
12
+ h[:a] #=> 3
13
+
14
+ Becuase the properties are fixed, if we try to set a key that is not present,
15
+ then we will get an error.
16
+
17
+ expect ArgumentError do
18
+ h[:x] = 5
19
+ end
20
+
21
+ The PropertyHash can also be used as a superclass.
22
+
23
+ class MyPropertyHash < PropertyHash
24
+ property :a, :default => 1
25
+ property :b, :default => 2
26
+ end
27
+
28
+ h = MyPropertyHash.new
29
+ h[:a] #=> 1
30
+ h[:a] = 3
31
+ h[:a] #=> 3
32
+
33
+ Again, if we try to set key that was not fixed, then we will get an error.
34
+
35
+ expect ArgumentError do
36
+ h[:x] = 5
37
+ end
38
+
@@ -0,0 +1,56 @@
1
+ = OpenStructable
2
+
3
+ OpensStructable is a mixin module which can provide OpenStruct behavior to
4
+ any class or object. OpenStructable allows extention of data objects
5
+ with arbitrary attributes.
6
+
7
+ require 'hashery/ostructable'
8
+
9
+ class Record
10
+ include OpenStructable
11
+ end
12
+
13
+ Now let's create a new record and see if we can assign values to open
14
+ properties.
15
+
16
+ record = Record.new
17
+ record.name = "John Smith"
18
+ record.age = 70
19
+ record.pension = 300
20
+
21
+ We can see that the values were set.
22
+
23
+ record.name.assert == "John Smith"
24
+ record.age.assert == 70
25
+ record.pension.assert == 300
26
+
27
+ If we havent' assigned a value to a property it should just return +nil+.
28
+
29
+ record.address.assert == nil
30
+
31
+ OpenStructable is also smart enough to adjust itself to work with a subclass
32
+ of a Hash.
33
+
34
+ class HashRecord < Hash
35
+ include OpenStructable
36
+ end
37
+
38
+ We can apply similar settings as above.
39
+
40
+ record = HashRecord.new
41
+ record.name = "John Doe"
42
+ record.age = 40
43
+ record.pension = 200
44
+
45
+ We can see that the values were set.
46
+
47
+ record.name.assert == "John Doe"
48
+ record.age.assert == 40
49
+ record.pension.assert == 200
50
+
51
+ The differnce here is that the data is accessible via the normal Hash
52
+ methods too.
53
+
54
+ record.assert == {:name=>"John Doe", :age=>40, :pension=>200}
55
+
56
+ Notice that entries are converted to Symbols, not Strings.