kissifer-hash-persistent 0.0.0 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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