kissifer-hash-persistent 0.0.0 → 0.0.3

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/Rakefile CHANGED
@@ -5,7 +5,7 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "hash-persistent"
8
- gem.summary = %Q{Library of base classes to simplify persisting objects and collections to a moneta store}
8
+ gem.summary = %Q{Library of base classes to simplify persisting objects in a moneta store}
9
9
  gem.email = "tierneydrchris@gmail.com"
10
10
  gem.homepage = "http://github.com/kissifer/hash-persistent"
11
11
  gem.authors = ["kissifer"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.3
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{hash-persistent}
5
- s.version = "0.0.0"
5
+ s.version = "0.0.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["kissifer"]
9
- s.date = %q{2009-06-23}
9
+ s.date = %q{2009-05-22}
10
10
  s.email = %q{tierneydrchris@gmail.com}
11
11
  s.extra_rdoc_files = [
12
12
  "LICENSE",
@@ -21,31 +21,22 @@ Gem::Specification.new do |s|
21
21
  "VERSION",
22
22
  "hash-persistent.gemspec",
23
23
  "lib/hash-persistent.rb",
24
- "lib/hash-persistent/collection.rb",
25
- "lib/hash-persistent/counter.rb",
26
- "lib/hash-persistent/extended_store.rb",
27
- "lib/hash-persistent/resource.rb",
28
- "lib/hash-persistent/store.rb",
29
- "spec/collection_spec.rb",
30
- "spec/counter_spec.rb",
31
- "spec/extended_store_spec.rb",
32
- "spec/resource_spec.rb",
24
+ "lib/hash-persistent/basic.rb",
25
+ "lib/hash-persistent/counting.rb",
26
+ "spec/basic_spec.rb",
27
+ "spec/counting_spec.rb",
33
28
  "spec/spec.opts",
34
- "spec/spec_helper.rb",
35
- "spec/store_spec.rb"
29
+ "spec/spec_helper.rb"
36
30
  ]
37
31
  s.homepage = %q{http://github.com/kissifer/hash-persistent}
38
32
  s.rdoc_options = ["--charset=UTF-8"]
39
33
  s.require_paths = ["lib"]
40
- s.rubygems_version = %q{1.3.4}
41
- s.summary = %q{Library of base classes to simplify persisting objects and collections to a moneta store}
34
+ s.rubygems_version = %q{1.3.3}
35
+ s.summary = %q{Library of base classes to simplify persisting objects in a moneta store}
42
36
  s.test_files = [
43
- "spec/collection_spec.rb",
44
- "spec/counter_spec.rb",
45
- "spec/extended_store_spec.rb",
46
- "spec/resource_spec.rb",
47
- "spec/spec_helper.rb",
48
- "spec/store_spec.rb"
37
+ "spec/basic_spec.rb",
38
+ "spec/counting_spec.rb",
39
+ "spec/spec_helper.rb"
49
40
  ]
50
41
 
51
42
  if s.respond_to? :specification_version then
@@ -1,8 +1,5 @@
1
1
  $LOAD_PATH << File.dirname(__FILE__)
2
2
 
3
3
  require 'rubygems'
4
- require 'hash-persistent/store'
5
- require 'hash-persistent/extended_store'
6
- require 'hash-persistent/resource'
7
- require 'hash-persistent/collection'
8
- require 'hash-persistent/counter'
4
+ require 'hash-persistent/basic'
5
+ require 'hash-persistent/counting'
@@ -0,0 +1,36 @@
1
+ module HashPersistent
2
+ class Basic
3
+ class << self
4
+ @@store = {}
5
+ @@prefix = ""
6
+
7
+ def setup(store, prefix)
8
+ raise ArgumentError unless store.respond_to?(:has_key?) and prefix.respond_to?(:to_s)
9
+ @@store = store
10
+ @@prefix = prefix
11
+ end
12
+
13
+ def find(key)
14
+ @@store[@@prefix + key]
15
+ end
16
+ end
17
+
18
+ attr_reader :key
19
+
20
+ def initialize(key)
21
+ raise RuntimeError if @@store.has_key?(key)
22
+ @key = key
23
+ @@store[@@prefix + @key] = self
24
+ end
25
+
26
+ def delete
27
+ raise RuntimeError unless @@store.has_key?(@@prefix + @key)
28
+ @@store.delete(@@prefix + @key)
29
+ end
30
+
31
+ def update
32
+ raise RuntimeError unless @@store.has_key?(@@prefix + @key)
33
+ @@store[@@prefix + @key] = self
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module HashPersistent
2
+ class Counting
3
+ class << self
4
+ @@store = {}
5
+ @@prefix = ""
6
+ @@next_key = 0
7
+ @@next_key_inc_lock = Mutex.new
8
+
9
+ def setup(store, prefix)
10
+ raise ArgumentError unless store.respond_to?(:has_key?) and prefix.respond_to?(:to_s)
11
+ @@store = store
12
+ @@prefix = prefix
13
+ end
14
+
15
+ def find(key)
16
+ @@store[@@prefix + key]
17
+ end
18
+ end
19
+
20
+ attr_reader :key
21
+
22
+ def initialize
23
+ @@next_key_inc_lock.synchronize do
24
+ @key = @@next_key.to_s
25
+ @@next_key += 1
26
+ end
27
+ @@store[@@prefix + @key] = self
28
+ end
29
+
30
+ def delete
31
+ raise RuntimeError unless @@store.has_key?(@@prefix + @key)
32
+ @@store.delete(@@prefix + @key)
33
+ end
34
+
35
+ def update
36
+ raise RuntimeError unless @@store.has_key?(@@prefix + @key)
37
+ @@store[@@prefix + @key] = self
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,188 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "HashPersistent::Basic" do
4
+ context "(before use)" do
5
+ it "should accept a hash-like store, and a string-like prefix, as class initialisers" do
6
+ HashPersistent::Basic.setup({}, "_prefix")
7
+ end
8
+
9
+ it "should reject a non-hash-like store" do
10
+ lambda {HashPersistent::Basic.setup(1, "_prefix")}.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it "should reject a non-string-like prefix" do
14
+ lambda {HashPersistent::Basic.setup({}, Dummy_NoStringRep.new)}.should raise_error(ArgumentError)
15
+ end
16
+ end
17
+
18
+ context "(creating objects)" do
19
+ it "should allow not objects to be created without a key" do
20
+ HashPersistent::Basic.setup({}, "")
21
+ lambda{HashPersistent::Basic.new}.should raise_error
22
+ end
23
+
24
+ it "should allow objects to be created with a key" do
25
+ HashPersistent::Basic.setup({}, "")
26
+ lambda{HashPersistent::Basic.new("a_key")}.should_not raise_error
27
+ end
28
+
29
+ it "should not allow objects to be created with a duplicate key" do
30
+ HashPersistent::Basic.setup({}, "")
31
+ HashPersistent::Basic.new("a_key")
32
+ lambda{HashPersistent::Basic.new("a_key")}.should raise_error
33
+ end
34
+
35
+ it "should return an object after creation" do
36
+ HashPersistent::Basic.setup({}, "")
37
+ new_object = HashPersistent::Basic.new("a_key")
38
+ new_object.class.should == HashPersistent::Basic
39
+ new_object.key.should == "a_key"
40
+ end
41
+ end
42
+
43
+ context "(retrieving objects)" do
44
+ it "should not return an object when no objects have been created" do
45
+ HashPersistent::Basic.setup({}, "")
46
+ HashPersistent::Basic.find("a_key").should be_nil
47
+ end
48
+
49
+ it "should return created objects when given the correct key" do
50
+ HashPersistent::Basic.setup({}, "")
51
+ HashPersistent::Basic.new("a_key")
52
+ new_object = HashPersistent::Basic.find("a_key")
53
+ new_object.class.should == HashPersistent::Basic
54
+ new_object.key.should == "a_key"
55
+ end
56
+
57
+ it "should not return an object when given an incorrect key" do
58
+ HashPersistent::Basic.setup({}, "")
59
+ HashPersistent::Basic.new("a_key")
60
+ HashPersistent::Basic.find("another_key").should be_nil
61
+ end
62
+ end
63
+
64
+ context "(deleting objects)" do
65
+ it "should delete an object when asked" do
66
+ HashPersistent::Basic.setup({}, "")
67
+ new_object = HashPersistent::Basic.new("a_key")
68
+ new_object.delete
69
+ HashPersistent::Basic.find("a_key").should be_nil
70
+ end
71
+
72
+ it "should not allow an object to be deleted twice" do
73
+ HashPersistent::Basic.setup({}, "")
74
+ new_object = HashPersistent::Basic.new("a_key")
75
+ new_object.delete
76
+ lambda{new_object.delete}.should raise_error
77
+ end
78
+ end
79
+
80
+ context "(key namespacing)" do
81
+ it "should (transparently) prepend the supplied prefix to the key of the created object" do
82
+ store = {}
83
+ HashPersistent::Basic.setup(store, "a_namespace::")
84
+ new_object = HashPersistent::Basic.new("a_key")
85
+ new_object.class.should == HashPersistent::Basic
86
+ new_object.key.should == "a_key"
87
+ store.should_not have_key("a_key")
88
+ store.should have_key("a_namespace::a_key")
89
+ end
90
+
91
+ it "should correctly find objects when given a namespace" do
92
+ HashPersistent::Basic.setup({}, "a_namespace::")
93
+ HashPersistent::Basic.new("a_key")
94
+ new_object = HashPersistent::Basic.find("a_key")
95
+ new_object.should_not be_nil
96
+ new_object.key.should == "a_key"
97
+ end
98
+
99
+ it "should not find objects when mistakenly given a namespaced key" do
100
+ HashPersistent::Basic.setup({}, "a_namespace::")
101
+ HashPersistent::Basic.new("a_key")
102
+ HashPersistent::Basic.find("a_namespace::a_key").should be_nil
103
+ end
104
+
105
+ it "should correctly delete objects when given a namespace" do
106
+ store = {}
107
+ HashPersistent::Basic.setup(store, "a_namespace::")
108
+ new_object = HashPersistent::Basic.new("a_key")
109
+ new_object.delete
110
+
111
+ HashPersistent::Basic.find("a_key").should be_nil
112
+ store.should_not have_key("a_namespace::a_key")
113
+ end
114
+ end
115
+
116
+
117
+ class Dummy_BasicDerivedClass < HashPersistent::Basic
118
+ attr_accessor :dummy
119
+
120
+ def initialize(key, dummy)
121
+ super(key)
122
+ @dummy = dummy
123
+ end
124
+ end
125
+
126
+ context "(derived classes)" do
127
+ it "should correctly store the full member list of a derived class" do
128
+ Dummy_BasicDerivedClass.setup({}, "")
129
+ created_dummy = Dummy_BasicDerivedClass.new("a_key", 123)
130
+ created_dummy.key.should == "a_key"
131
+ created_dummy.dummy.should == 123
132
+
133
+ retrieved_dummy = Dummy_BasicDerivedClass.find("a_key")
134
+ retrieved_dummy.key.should == "a_key"
135
+ retrieved_dummy.dummy.should == 123
136
+ end
137
+ end
138
+
139
+ context "(updating objects)" do
140
+ it "should correctly update members of a derived class" do
141
+ Dummy_BasicDerivedClass.setup({}, "prefix::")
142
+ created_dummy = Dummy_BasicDerivedClass.new("a_key", 123)
143
+
144
+ duplicated_dummy = created_dummy.dup
145
+ duplicated_dummy.dummy = 456
146
+ duplicated_dummy.update
147
+
148
+ retrieved_dummy = Dummy_BasicDerivedClass.find("a_key")
149
+ retrieved_dummy.dummy.should_not == 123
150
+ retrieved_dummy.dummy.should == 456
151
+ end
152
+
153
+ it "should not allow a deleted object to update" do
154
+ Dummy_BasicDerivedClass.setup({}, "prefix::")
155
+ created_dummy = Dummy_BasicDerivedClass.new("a_key", 123)
156
+ created_dummy.delete
157
+ created_dummy.dummy = 456
158
+ lambda{created_dummy.update}.should raise_error
159
+ end
160
+ end
161
+
162
+ context "(restricted hash api)" do
163
+ it "should use only the restricted hash api provided by moneta" do
164
+ lambda {
165
+ Dummy_BasicDerivedClass.setup(Dummy_RestrictedHash.new, "prefix::")
166
+ created_dummy = Dummy_BasicDerivedClass.new("a_key", 123)
167
+
168
+ duplicated_dummy = created_dummy.dup
169
+ duplicated_dummy.dummy = 456
170
+ duplicated_dummy.update
171
+
172
+ retrieved_dummy = Dummy_BasicDerivedClass.find("a_key")
173
+ retrieved_dummy.delete
174
+ }.should_not raise_error
175
+ end
176
+ end
177
+
178
+ context "(concurrent access)" do
179
+ it "should be careful doing various operations" do
180
+ # What to test here? Duplicate in the other similar classes
181
+ # Test: Update, update vs delete, delete vs find, update vs find
182
+ # Eugh, how best to do this? Options:
183
+ # Subclass Hash and slow the set/get methods down?
184
+ # Monkey patch the Mutex class to add lock/unlock callbacks?
185
+ pending
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,196 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "HashPersistent::Counting" do
4
+ context "(before use)" do
5
+ it "should accept a hash-like store, and a string-like prefix, as class initialisers" do
6
+ HashPersistent::Counting.setup({}, "_prefix")
7
+ end
8
+
9
+ it "should reject a non-hash-like store" do
10
+ lambda {HashPersistent::Counting.setup(1, "_prefix")}.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it "should reject a non-string-like prefix" do
14
+ lambda {HashPersistent::Counting.setup({}, Dummy_NoStringRep.new)}.should raise_error(ArgumentError)
15
+ end
16
+ end
17
+
18
+ context "(creating objects)" do
19
+ it "should not allow objects to be created with a key" do
20
+ HashPersistent::Counting.setup({}, "")
21
+ lambda{HashPersistent::Counting.new("a_key")}.should raise_error
22
+ end
23
+
24
+ it "should allow objects to be created without a key" do
25
+ HashPersistent::Counting.setup({}, "")
26
+ lambda{HashPersistent::Counting.new}.should_not raise_error
27
+ end
28
+
29
+ it "should return an object after creation" do
30
+ HashPersistent::Counting.setup({}, "")
31
+ new_object = HashPersistent::Counting.new
32
+ new_object.class.should == HashPersistent::Counting
33
+ end
34
+
35
+ it "generate unique keys for each object creation" do
36
+ HashPersistent::Counting.setup({}, "")
37
+ keys = []
38
+ lots = 100 # How many is enough here? We just have to pick a number I guess...
39
+ lots.times do
40
+ keys << HashPersistent::Counting.new.key
41
+ end
42
+ keys.uniq.should == keys
43
+ end
44
+ end
45
+
46
+ context "(retrieving objects)" do
47
+ it "should not return an object when no objects have been created" do
48
+ HashPersistent::Counting.setup({}, "")
49
+ HashPersistent::Counting.find("1").should be_nil
50
+ end
51
+
52
+ it "should return created objects when given the correct key" do
53
+ HashPersistent::Counting.setup({}, "")
54
+ key = HashPersistent::Counting.new.key
55
+ new_object = HashPersistent::Counting.find(key)
56
+ new_object.class.should == HashPersistent::Counting
57
+ new_object.key.should == key
58
+ end
59
+
60
+ it "should not return an object when given an incorrect key" do
61
+ HashPersistent::Counting.setup({}, "")
62
+ key = HashPersistent::Counting.new.key.to_s + "_not_a_valid_key"
63
+ HashPersistent::Counting.find(key).should be_nil
64
+ end
65
+ end
66
+
67
+ context "(deleting objects)" do
68
+ it "should delete an object when asked" do
69
+ HashPersistent::Counting.setup({}, "")
70
+ new_object = HashPersistent::Counting.new
71
+ key = new_object.key
72
+ new_object.delete
73
+ HashPersistent::Counting.find(key).should be_nil
74
+ end
75
+
76
+ it "should not allow an object to be deleted twice" do
77
+ HashPersistent::Counting.setup({}, "")
78
+ new_object = HashPersistent::Counting.new
79
+ new_object.delete
80
+ lambda{new_object.delete}.should raise_error
81
+ end
82
+ end
83
+
84
+ context "(key namespacing)" do
85
+ it "should (transparently) prepend the supplied prefix to the key of the created object" do
86
+ store = {}
87
+ HashPersistent::Counting.setup(store, "a_namespace::")
88
+ new_object = HashPersistent::Counting.new
89
+ new_object.class.should == HashPersistent::Counting
90
+ store.should_not have_key("#{new_object.key}")
91
+ store.should_not have_key(new_object.key)
92
+ store.should have_key("a_namespace::#{new_object.key}")
93
+ end
94
+
95
+ it "should correctly find objects when given a namespace" do
96
+ HashPersistent::Counting.setup({}, "a_namespace::")
97
+ key = HashPersistent::Counting.new.key
98
+ new_object = HashPersistent::Counting.find(key)
99
+ new_object.should_not be_nil
100
+ new_object.key.should == key
101
+ end
102
+
103
+ it "should not find objects when mistakenly given a namespaced key" do
104
+ HashPersistent::Counting.setup({}, "a_namespace::")
105
+ key = HashPersistent::Counting.new
106
+ HashPersistent::Counting.find("a_namespace::#{key}").should be_nil
107
+ end
108
+
109
+ it "should correctly delete objects when given a namespace" do
110
+ store = {}
111
+ HashPersistent::Counting.setup(store, "a_namespace::")
112
+ new_object = HashPersistent::Counting.new
113
+ key = new_object.key
114
+ new_object.delete
115
+
116
+ HashPersistent::Counting.find(key).should be_nil
117
+ store.should_not have_key("a_namespace::#{key}")
118
+ end
119
+ end
120
+
121
+
122
+ class Dummy_CountingDerivedClass < HashPersistent::Counting
123
+ attr_accessor :dummy
124
+
125
+ def initialize(dummy)
126
+ super()
127
+ @dummy = dummy
128
+ end
129
+ end
130
+
131
+ context "(derived classes)" do
132
+ it "should correctly store the full member list of a derived class" do
133
+ Dummy_CountingDerivedClass.setup({}, "")
134
+ created_dummy = Dummy_CountingDerivedClass.new(123)
135
+ created_dummy.dummy.should == 123
136
+
137
+ retrieved_dummy = Dummy_CountingDerivedClass.find(created_dummy.key)
138
+ retrieved_dummy.key.should == created_dummy.key
139
+ retrieved_dummy.dummy.should == 123
140
+ end
141
+ end
142
+
143
+ context "(updating objects)" do
144
+ it "should correctly update members of a derived class" do
145
+ Dummy_CountingDerivedClass.setup({}, "prefix::")
146
+ created_dummy = Dummy_CountingDerivedClass.new(123)
147
+ key = created_dummy.key
148
+
149
+ duplicated_dummy = created_dummy.dup
150
+ duplicated_dummy.dummy = 456
151
+ duplicated_dummy.update
152
+
153
+ retrieved_dummy = Dummy_CountingDerivedClass.find(key)
154
+ retrieved_dummy.dummy.should_not == 123
155
+ retrieved_dummy.dummy.should == 456
156
+
157
+ retrieved_dummy.key.should == key
158
+ end
159
+
160
+ it "should not allow a deleted object to update" do
161
+ Dummy_CountingDerivedClass.setup({}, "prefix::")
162
+ created_dummy = Dummy_CountingDerivedClass.new(123)
163
+ created_dummy.delete
164
+ created_dummy.dummy = 456
165
+ lambda{created_dummy.update}.should raise_error
166
+ end
167
+ end
168
+
169
+ context "(restricted hash api)" do
170
+ it "should use only the restricted hash api provided by moneta" do
171
+ lambda {
172
+ Dummy_CountingDerivedClass.setup(Dummy_RestrictedHash.new, "prefix::")
173
+ created_dummy = Dummy_CountingDerivedClass.new(123)
174
+ key = created_dummy.key
175
+
176
+ duplicated_dummy = created_dummy.dup
177
+ duplicated_dummy.dummy = 456
178
+ duplicated_dummy.update
179
+
180
+ retrieved_dummy = Dummy_CountingDerivedClass.find(key)
181
+ retrieved_dummy.delete
182
+ }.should_not raise_error
183
+ end
184
+ end
185
+
186
+ context "(concurrent access)" do
187
+ it "should be careful incrementing the auto-generated key" do
188
+ # TODO. Subclass Hash, adda delay into the []= method (or another one?)
189
+ # Kick off two threads and create a bunch of objects
190
+ # Collect keys into arrays, merge, sort and compare for equality with
191
+ # an array of sequential numbers the same size as the number of objects
192
+ # we created
193
+ pending
194
+ end
195
+ end
196
+ end