kissifer-hash-persistent 0.1.1 → 0.2.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/VERSION +1 -1
- data/hash-persistent.gemspec +6 -3
- data/lib/hash-persistent.rb +1 -0
- data/lib/hash-persistent/collection.rb +50 -0
- data/lib/hash-persistent/resource.rb +22 -2
- data/spec/collection_spec.rb +195 -0
- data/spec/counter_spec.rb +1 -3
- data/spec/resource_spec.rb +59 -5
- metadata +5 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/hash-persistent.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{hash-persistent}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.2.0"
|
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-05-
|
9
|
+
s.date = %q{2009-05-28}
|
10
10
|
s.email = %q{tierneydrchris@gmail.com}
|
11
11
|
s.extra_rdoc_files = [
|
12
12
|
"LICENSE",
|
@@ -21,8 +21,10 @@ 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",
|
24
25
|
"lib/hash-persistent/counter.rb",
|
25
26
|
"lib/hash-persistent/resource.rb",
|
27
|
+
"spec/collection_spec.rb",
|
26
28
|
"spec/counter_spec.rb",
|
27
29
|
"spec/resource_spec.rb",
|
28
30
|
"spec/spec.opts",
|
@@ -34,7 +36,8 @@ Gem::Specification.new do |s|
|
|
34
36
|
s.rubygems_version = %q{1.3.3}
|
35
37
|
s.summary = %q{Library of base classes to simplify persisting objects in a moneta store}
|
36
38
|
s.test_files = [
|
37
|
-
"spec/
|
39
|
+
"spec/collection_spec.rb",
|
40
|
+
"spec/counter_spec.rb",
|
38
41
|
"spec/resource_spec.rb",
|
39
42
|
"spec/spec_helper.rb"
|
40
43
|
]
|
data/lib/hash-persistent.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
module HashPersistent
|
2
|
+
module Collection
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.module_eval do
|
7
|
+
include HashPersistent::Resource
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def attach(resource_class, collection_basis)
|
13
|
+
raise ArgumentError unless resource_class.included_modules.include?(HashPersistent::Resource)
|
14
|
+
raise ArgumentError unless resource_class.new.respond_to?(collection_basis)
|
15
|
+
@collection_basis = collection_basis
|
16
|
+
resource_class.on_save do |resource|
|
17
|
+
collected_resource_saved(resource)
|
18
|
+
end
|
19
|
+
|
20
|
+
resource_class.on_delete do |resource|
|
21
|
+
collected_resource_deleted(resource)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def collected_resource_saved(resource)
|
26
|
+
collection = find(resource.send(@collection_basis))
|
27
|
+
unless collection
|
28
|
+
collection = new
|
29
|
+
collection.collected_keys = []
|
30
|
+
collection.key = resource.send(@collection_basis)
|
31
|
+
end
|
32
|
+
collection.collected_keys << resource.key
|
33
|
+
collection.collected_keys.uniq!
|
34
|
+
collection.save
|
35
|
+
end
|
36
|
+
|
37
|
+
def collected_resource_deleted(resource)
|
38
|
+
collection = find(resource.send(@collection_basis))
|
39
|
+
collection.collected_keys.delete(resource.key)
|
40
|
+
if collection.collected_keys.empty?
|
41
|
+
collection.delete
|
42
|
+
else
|
43
|
+
collection.save
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_accessor :collected_keys
|
49
|
+
end
|
50
|
+
end
|
@@ -18,6 +18,22 @@ module HashPersistent
|
|
18
18
|
def find(key)
|
19
19
|
@store[@prefix + key]
|
20
20
|
end
|
21
|
+
|
22
|
+
def on_save(&block)
|
23
|
+
if block
|
24
|
+
@on_save = block
|
25
|
+
else
|
26
|
+
@on_save
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_delete(&block)
|
31
|
+
if block
|
32
|
+
@on_delete = block
|
33
|
+
else
|
34
|
+
@on_delete
|
35
|
+
end
|
36
|
+
end
|
21
37
|
|
22
38
|
attr_reader :store, :prefix
|
23
39
|
end
|
@@ -31,13 +47,17 @@ module HashPersistent
|
|
31
47
|
def save
|
32
48
|
raise RuntimeError unless key
|
33
49
|
self.class.store[prefix_key] = self
|
50
|
+
self.class.on_save.call(self) if self.class.on_save
|
34
51
|
end
|
35
52
|
|
36
53
|
def delete
|
37
|
-
raise RuntimeError unless
|
54
|
+
raise RuntimeError unless key
|
55
|
+
return unless self.class.store.has_key?(prefix_key)
|
56
|
+
# TODO concurrency here, the store's delete call should cope, but we don't want the callback if something else got in first?
|
57
|
+
# Alternative: ensure that callback handles this case, which is true of HashPersistent::Collection
|
38
58
|
self.class.store.delete(prefix_key)
|
59
|
+
self.class.on_delete.call(self) if self.class.on_delete
|
39
60
|
end
|
40
|
-
|
41
61
|
end
|
42
62
|
end
|
43
63
|
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class CollectionFoo
|
4
|
+
include HashPersistent::Collection
|
5
|
+
end
|
6
|
+
|
7
|
+
class CollectedResource
|
8
|
+
include HashPersistent::Resource
|
9
|
+
attr_accessor :basis
|
10
|
+
end
|
11
|
+
|
12
|
+
class NotAResource
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "A class that includes HashPersistent::Collection" do
|
16
|
+
it "should include the HashPersistent::Resource module" do
|
17
|
+
CollectionFoo.included_modules.should include (HashPersistent::Resource)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow itself to be attached to another HashPersistent::Resource, specifying an attribute/method for the collection basis" do
|
21
|
+
lambda{CollectionFoo.attach(CollectedResource, :basis)}.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not attach to the wrong class type" do
|
25
|
+
lambda{CollectionFoo.attach(NotAResource, :basis)}.should raise_error
|
26
|
+
lambda{CollectionFoo.attach(1, :basis)}.should raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should verify a valid collection basis" do
|
30
|
+
lambda{CollectionFoo.attach(CollectedResource)}.should raise_error
|
31
|
+
lambda{CollectionFoo.attach(CollectedResource, :not_a_valid_attr)}.should raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
context "(when attached resources are saved/deleted)" do
|
35
|
+
it "should create a collection for a new basis" do
|
36
|
+
CollectionFoo.persist_to({}, "")
|
37
|
+
CollectedResource.persist_to({}, "")
|
38
|
+
|
39
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
40
|
+
CollectionFoo.find("the_basis").should == nil
|
41
|
+
|
42
|
+
resource = CollectedResource.new
|
43
|
+
resource.basis = "the_basis"
|
44
|
+
resource.key = "fred"
|
45
|
+
resource.save
|
46
|
+
|
47
|
+
CollectionFoo.find("the_basis").should_not == nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should report keys saved/added to the collection" do
|
51
|
+
CollectionFoo.persist_to({}, "")
|
52
|
+
CollectedResource.persist_to({}, "")
|
53
|
+
|
54
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
55
|
+
|
56
|
+
resource = CollectedResource.new
|
57
|
+
resource.basis = "the_basis"
|
58
|
+
resource.key = "fred"
|
59
|
+
resource.save
|
60
|
+
|
61
|
+
CollectionFoo.find("the_basis").collected_keys.should == ["fred"]
|
62
|
+
|
63
|
+
resource = CollectedResource.new
|
64
|
+
resource.basis = "the_basis"
|
65
|
+
resource.key = "barney"
|
66
|
+
resource.save
|
67
|
+
|
68
|
+
CollectionFoo.find("the_basis").collected_keys.should == ["fred", "barney"]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not duplicate keys when a resource is saved multiple times" do
|
72
|
+
CollectionFoo.persist_to({}, "")
|
73
|
+
CollectedResource.persist_to({}, "")
|
74
|
+
|
75
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
76
|
+
|
77
|
+
resource = CollectedResource.new
|
78
|
+
resource.basis = "the_basis"
|
79
|
+
resource.key = "fred"
|
80
|
+
resource.save
|
81
|
+
resource.save
|
82
|
+
|
83
|
+
CollectionFoo.find("the_basis").collected_keys.should == ["fred"]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not report keys with the wrong basis" do
|
87
|
+
CollectionFoo.persist_to({}, "")
|
88
|
+
CollectedResource.persist_to({}, "")
|
89
|
+
|
90
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
91
|
+
|
92
|
+
resource = CollectedResource.new
|
93
|
+
resource.basis = "one_basis"
|
94
|
+
resource.key = "fred"
|
95
|
+
resource.save
|
96
|
+
|
97
|
+
CollectionFoo.find("another_basis").should == nil
|
98
|
+
|
99
|
+
resource = CollectedResource.new
|
100
|
+
resource.basis = "another_basis"
|
101
|
+
resource.key = "barney"
|
102
|
+
resource.save
|
103
|
+
|
104
|
+
CollectionFoo.find("one_basis").collected_keys.should == ["fred"]
|
105
|
+
CollectionFoo.find("another_basis").collected_keys.should == ["barney"]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should remove the key when a resource is deleted" do
|
109
|
+
CollectionFoo.persist_to({}, "")
|
110
|
+
CollectedResource.persist_to({}, "")
|
111
|
+
|
112
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
113
|
+
|
114
|
+
resource = CollectedResource.new
|
115
|
+
resource.basis = "the_basis"
|
116
|
+
resource.key = "fred"
|
117
|
+
resource.save
|
118
|
+
|
119
|
+
resource = CollectedResource.new
|
120
|
+
resource.basis = "the_basis"
|
121
|
+
resource.key = "barney"
|
122
|
+
resource.save
|
123
|
+
resource.delete
|
124
|
+
|
125
|
+
CollectionFoo.find("the_basis").collected_keys.should == ["fred"]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should not complain when a not-present key is removed (either not added, or deleted twice)" do
|
129
|
+
CollectionFoo.persist_to({}, "")
|
130
|
+
CollectedResource.persist_to({}, "")
|
131
|
+
|
132
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
133
|
+
|
134
|
+
resource = CollectedResource.new
|
135
|
+
resource.basis = "the_basis"
|
136
|
+
resource.key = "fred"
|
137
|
+
resource.save
|
138
|
+
|
139
|
+
resource = CollectedResource.new
|
140
|
+
resource.basis = "the_basis"
|
141
|
+
resource.key = "barney"
|
142
|
+
|
143
|
+
lambda{resource.delete}.should_not raise_error
|
144
|
+
|
145
|
+
resource.save
|
146
|
+
resource.delete
|
147
|
+
lambda{resource.delete}.should_not raise_error
|
148
|
+
|
149
|
+
CollectionFoo.find("the_basis").collected_keys.should == ["fred"]
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not complain when a resource with an unknown basis is removed" do
|
153
|
+
CollectionFoo.persist_to({}, "")
|
154
|
+
CollectedResource.persist_to({}, "")
|
155
|
+
|
156
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
157
|
+
|
158
|
+
resource = CollectedResource.new
|
159
|
+
resource.basis = "the_basis"
|
160
|
+
resource.key = "fred"
|
161
|
+
|
162
|
+
lambda{resource.delete}.should_not raise_error
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should remove the collection when its final key is removed" do
|
166
|
+
CollectionFoo.persist_to({}, "")
|
167
|
+
CollectedResource.persist_to({}, "")
|
168
|
+
|
169
|
+
CollectionFoo.attach(CollectedResource, :basis)
|
170
|
+
|
171
|
+
resource = CollectedResource.new
|
172
|
+
resource.basis = "the_basis"
|
173
|
+
resource.key = "fred"
|
174
|
+
resource.save
|
175
|
+
|
176
|
+
resource = CollectedResource.new
|
177
|
+
resource.basis = "the_basis"
|
178
|
+
resource.key = "barney"
|
179
|
+
resource.save
|
180
|
+
|
181
|
+
CollectedResource.find("fred").delete
|
182
|
+
CollectedResource.find("barney").delete
|
183
|
+
|
184
|
+
CollectionFoo.find("the_basis").should == nil
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
context "(concurrent access)" do
|
190
|
+
it "should be careful when creating and updating the collection resources"
|
191
|
+
# Cases to cover here...
|
192
|
+
# - delete last basis and add basis race condition
|
193
|
+
# - add basis and add basis race condition
|
194
|
+
end
|
195
|
+
end
|
data/spec/counter_spec.rb
CHANGED
@@ -16,9 +16,7 @@ describe "HashPersistent::Counter" do
|
|
16
16
|
CounterFoo.next_key.should_not == CounterFoo.next_key
|
17
17
|
end
|
18
18
|
|
19
|
-
it "should be careful when incrementing the key in multi-threaded/process environment"
|
20
|
-
pending
|
21
|
-
end
|
19
|
+
it "should be careful when incrementing the key in multi-threaded/process environment"
|
22
20
|
end
|
23
21
|
|
24
22
|
context "when mixed-in to more than one class" do
|
data/spec/resource_spec.rb
CHANGED
@@ -63,12 +63,15 @@ describe "A class that includes HashPersistent::Resource" do
|
|
63
63
|
lambda{ResourceFoo.new.save}.should raise_error
|
64
64
|
end
|
65
65
|
|
66
|
-
it "should not
|
66
|
+
it "should not delete unless the key has been explictly set" do
|
67
|
+
lambda{ResourceFoo.new.delete}.should raise_error
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not complain when deleted, even after a key is set" do
|
67
71
|
ResourceFoo.persist_to(Hash.new, "")
|
68
72
|
resource = ResourceFoo.new
|
69
|
-
lambda{resource.delete}.should raise_error
|
70
73
|
resource.key = "fred"
|
71
|
-
lambda{resource.delete}.
|
74
|
+
lambda{resource.delete}.should_not raise_error
|
72
75
|
end
|
73
76
|
|
74
77
|
it "should not be findable via its key" do
|
@@ -135,13 +138,13 @@ describe "A class that includes HashPersistent::Resource" do
|
|
135
138
|
ResourceFoo.find("fred").should == nil
|
136
139
|
end
|
137
140
|
|
138
|
-
it "should not
|
141
|
+
it "should not complain when deleted" do
|
139
142
|
ResourceFoo.persist_to(Hash.new, "")
|
140
143
|
resource = ResourceFoo.new
|
141
144
|
resource.key = "fred"
|
142
145
|
resource.save
|
143
146
|
resource.delete
|
144
|
-
lambda{resource.delete}.
|
147
|
+
lambda{resource.delete}.should_not raise_error
|
145
148
|
end
|
146
149
|
end
|
147
150
|
|
@@ -230,6 +233,7 @@ describe "A class that includes HashPersistent::Resource" do
|
|
230
233
|
end
|
231
234
|
|
232
235
|
context "(multiple classes using module)" do
|
236
|
+
# TODO: should check the callbacks here? Seems over the top...
|
233
237
|
it "should not cross-contaminate classes that include the module" do
|
234
238
|
store_1 = Hash.new
|
235
239
|
ResourceFoo.persist_to(store_1, "")
|
@@ -268,4 +272,54 @@ describe "A class that includes HashPersistent::Resource" do
|
|
268
272
|
store_2.should == Hash.new
|
269
273
|
end
|
270
274
|
end
|
275
|
+
|
276
|
+
context "(callbacks)" do
|
277
|
+
it "should allow a class level callback to be set and retrieved for save events" do
|
278
|
+
ResourceFoo.on_save.should == nil
|
279
|
+
lambda{ResourceFoo.on_save do
|
280
|
+
"thing"
|
281
|
+
end}.should_not raise_error
|
282
|
+
ResourceFoo.on_save.call.should == "thing"
|
283
|
+
lambda{ResourceFoo.on_save(1)}.should raise_error
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should allow a class level callback to be set and retrieved for delete events" do
|
287
|
+
ResourceFoo.on_delete.should == nil
|
288
|
+
lambda{ResourceFoo.on_delete do
|
289
|
+
"thing"
|
290
|
+
end}.should_not raise_error
|
291
|
+
ResourceFoo.on_delete.call.should == "thing"
|
292
|
+
lambda{ResourceFoo.on_delete(1)}.should raise_error
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should yield an instance to the save callback when the instance is saved" do
|
296
|
+
yielded_instance = nil
|
297
|
+
ResourceFoo.on_save do |yielded|
|
298
|
+
yielded_instance = yielded
|
299
|
+
end
|
300
|
+
|
301
|
+
instance = ResourceFoo.new
|
302
|
+
instance.key = "1"
|
303
|
+
instance.save
|
304
|
+
|
305
|
+
yielded_instance.should_not == nil
|
306
|
+
yielded_instance.should == instance
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should yield an instance to the delete callback when the instance is deleted" do
|
310
|
+
yielded_instance = nil
|
311
|
+
ResourceFoo.on_delete do |yielded|
|
312
|
+
yielded_instance = yielded
|
313
|
+
end
|
314
|
+
|
315
|
+
instance = ResourceFoo.new
|
316
|
+
instance.key = "1"
|
317
|
+
instance.save
|
318
|
+
instance.delete
|
319
|
+
|
320
|
+
yielded_instance.should_not == nil
|
321
|
+
yielded_instance.should == instance
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
271
325
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kissifer-hash-persistent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kissifer
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-28 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,8 +31,10 @@ files:
|
|
31
31
|
- VERSION
|
32
32
|
- hash-persistent.gemspec
|
33
33
|
- lib/hash-persistent.rb
|
34
|
+
- lib/hash-persistent/collection.rb
|
34
35
|
- lib/hash-persistent/counter.rb
|
35
36
|
- lib/hash-persistent/resource.rb
|
37
|
+
- spec/collection_spec.rb
|
36
38
|
- spec/counter_spec.rb
|
37
39
|
- spec/resource_spec.rb
|
38
40
|
- spec/spec.opts
|
@@ -64,6 +66,7 @@ signing_key:
|
|
64
66
|
specification_version: 3
|
65
67
|
summary: Library of base classes to simplify persisting objects in a moneta store
|
66
68
|
test_files:
|
69
|
+
- spec/collection_spec.rb
|
67
70
|
- spec/counter_spec.rb
|
68
71
|
- spec/resource_spec.rb
|
69
72
|
- spec/spec_helper.rb
|