kissifer-hash-persistent 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 kissifer
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.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = hash-persistent
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 kissifer. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "hash-persistent"
8
+ gem.summary = %Q{Library of base classes to simplify persisting objects and collections to a moneta store}
9
+ gem.email = "tierneydrchris@gmail.com"
10
+ gem.homepage = "http://github.com/kissifer/hash-persistent"
11
+ gem.authors = ["kissifer"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ spec.spec_opts = ['--options spec/spec.opts']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+
33
+ task :default => :spec
34
+
35
+ require 'rake/rdoctask'
36
+ Rake::RDocTask.new do |rdoc|
37
+ if File.exist?('VERSION.yml')
38
+ config = YAML.load(File.read('VERSION.yml'))
39
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "hash-persistent #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,60 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{hash-persistent}
5
+ s.version = "0.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["kissifer"]
9
+ s.date = %q{2009-06-23}
10
+ s.email = %q{tierneydrchris@gmail.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "hash-persistent.gemspec",
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",
33
+ "spec/spec.opts",
34
+ "spec/spec_helper.rb",
35
+ "spec/store_spec.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/kissifer/hash-persistent}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ 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}
42
+ 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"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ else
57
+ end
58
+ else
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/resource')
2
+
3
+ module HashPersistent
4
+ module Collection
5
+ include HashPersistent::Resource
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ attr_accessor :basis
13
+
14
+ def on_watched_store_save_event(store_for_collection, saved_object)
15
+ raise "Cannot collect objects without a basis for the collection. Set :basis class attribute" unless @basis
16
+ return unless saved_object.respond_to?(:basis) && saved_object.respond_to?(:key)
17
+
18
+ collection_resource = store_for_collection.find(saved_object.send(:basis))
19
+ unless collection_resource
20
+ collection_resource = new
21
+ collection_resource.store = store_for_collection
22
+ collection_resource.key = saved_object.send(:basis)
23
+ end
24
+
25
+ collection_resource.collected_keys << saved_object.send(:key)
26
+ collection_resource.collected_keys.uniq!
27
+ collection_resource.save
28
+ end
29
+
30
+ def on_watched_store_delete_event(store_for_collection, deleted_object)
31
+ raise "Cannot collect objects without a basis for the collection. Set :basis class attribute" unless @basis
32
+ return unless deleted_object.respond_to?(:basis) && deleted_object.respond_to?(:key)
33
+
34
+ collection_resource = store_for_collection.find(deleted_object.send(:basis))
35
+ return unless collection_resource
36
+
37
+ collection_resource.collected_keys.delete(deleted_object.send(:key))
38
+
39
+ if collection_resource.collected_keys == []
40
+ collection_resource.delete
41
+ else
42
+ collection_resource.save
43
+ end
44
+ end
45
+ end
46
+
47
+ def collected_keys
48
+ @collected_keys ||= []
49
+ end
50
+
51
+ def collected_resources
52
+ @collected_keys.collect do |key|
53
+ resource = store.watching.find(key)
54
+ raise "Collected key not found in watched store" unless resource
55
+ resource
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ require 'ostruct'
2
+
3
+ module HashPersistent
4
+ class Counter
5
+ def initialize(store, key)
6
+ raise ArgumentError unless store.is_a?(HashPersistent::Store) and key.respond_to?(:to_s) and key.to_s.length != 0
7
+ @store = store
8
+ @key = key
9
+
10
+ unless @store.find(@key)
11
+ counter = OpenStruct.new
12
+ counter.key = @key
13
+ counter.count = 0
14
+ @store.save(counter)
15
+ end
16
+ end
17
+
18
+ def next
19
+ counter = @store.find(@key)
20
+ next_count = counter.count
21
+ counter.count += 1
22
+ @store.save(counter)
23
+ next_count
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/store')
2
+
3
+ module HashPersistent
4
+ class ExtendedStore
5
+ attr_reader :store
6
+
7
+ def initialize(store)
8
+ @store = store
9
+ yield self if block_given?
10
+ end
11
+
12
+ def find(key)
13
+ a_singleton = singleton_keys.find do |singleton|
14
+ key.match(singleton[0])
15
+ end
16
+
17
+ if a_singleton
18
+ find_or_create(key, a_singleton[1], *a_singleton[2], &a_singleton[3])
19
+ else
20
+ @store.find(key)
21
+ end
22
+ end
23
+
24
+ def save(object)
25
+ @store.save(object)
26
+ end
27
+
28
+ def delete(object)
29
+ @store.delete(object)
30
+ end
31
+
32
+ def create(key, class_to_create, *args, &block)
33
+ @store.create(key, class_to_create, *args, &block)
34
+ end
35
+
36
+ def find_or_create(key, class_to_create, *args, &block)
37
+ @store.find_or_create(key, class_to_create, *args, &block)
38
+ end
39
+
40
+ def singleton_key(pattern, class_to_create, *args, &block)
41
+ "1".match(pattern) # a test to check the duck typing early
42
+ singleton_keys << [pattern, class_to_create, args, block]
43
+ end
44
+
45
+ def singleton_keys
46
+ @singleton_keys ||= []
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ module HashPersistent
2
+ module Resource
3
+ attr_accessor :store, :key
4
+
5
+ def save
6
+ @store.save(self) if @store
7
+ end
8
+
9
+ def delete
10
+ @store.delete(self) if @store
11
+ end
12
+
13
+ def move(new_store)
14
+ delete
15
+ @store = new_store
16
+ save
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,67 @@
1
+ module HashPersistent
2
+ class Store
3
+ attr_reader :key_value_store, :key_prefix, :cached_to
4
+
5
+ def initialize(key_value_store, key_prefix)
6
+ raise ArgumentError unless key_value_store.respond_to?(:has_key?) and key_prefix.respond_to?(:to_s)
7
+ @key_value_store = key_value_store
8
+ @key_prefix = key_prefix
9
+ yield self if block_given?
10
+ end
11
+
12
+ def cached_to=(cache)
13
+ raise ArgumentError unless cache.is_a?(HashPersistent::Store) if cache
14
+ @cached_to = cache
15
+ end
16
+
17
+ def save(object)
18
+ store_this = object.dup
19
+ store_this.store = nil if store_this.respond_to?(:store)
20
+ @key_value_store[@key_prefix + store_this.key] = store_this
21
+
22
+ @cached_to.save(object) if @cached_to
23
+
24
+ object.store = self if object.respond_to?(:store)
25
+ self
26
+ end
27
+
28
+ def delete(object)
29
+ @key_value_store.delete(@key_prefix + object.key)
30
+ @cached_to.delete(object) if @cached_to
31
+
32
+ object.store = self if object.respond_to?(:store)
33
+ self
34
+ end
35
+
36
+ def find(key)
37
+ if @cached_to
38
+ found = @cached_to.find(key)
39
+ unless found
40
+ found = @key_value_store[@key_prefix + key]
41
+ @cached_to.save(found) if found
42
+ end
43
+ else
44
+ found = @key_value_store[@key_prefix + key]
45
+ end
46
+ return nil unless found
47
+
48
+ object = found.dup
49
+ object.store = self if object.respond_to?(:store)
50
+
51
+ object
52
+ end
53
+
54
+ def create(key, class_to_create, *args, &block)
55
+ object = class_to_create.new(*args, &block)
56
+ object.key = key
57
+ object.store = self if object.respond_to?(:store)
58
+ object
59
+ end
60
+
61
+ def find_or_create(key, class_to_create, *args, &block)
62
+ #TODO: Lock?
63
+ save(create(key, class_to_create, *args, &block)) unless find(key)
64
+ find(key)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+
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'
@@ -0,0 +1,269 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'ostruct'
3
+
4
+ __END__
5
+
6
+ class CollectionFoo
7
+ include HashPersistent::Collection
8
+ end
9
+
10
+ class CollectionDoNotSetABasis
11
+ include HashPersistent::Collection
12
+ end
13
+
14
+ describe "A class that includes HashPersistent::Collection" do
15
+ it "should include the HashPersistent::Resource module" do
16
+ CollectionFoo.included_modules.should include(HashPersistent::Resource)
17
+ end
18
+
19
+ it "should acquire on_watched_store_save_event() and on_watched_store_delete_event() methods" do
20
+ CollectionFoo.respond_to?(:on_watched_store_save_event).should be_true
21
+ CollectionFoo.respond_to?(:on_watched_store_delete_event).should be_true
22
+ end
23
+
24
+ it "should acquire a basis attribute" do
25
+ CollectionFoo.basis = "foo"
26
+ CollectionFoo.basis.should == "foo"
27
+ end
28
+
29
+ context "(when not given a basis)" do
30
+ it "should complain when given a save event" do
31
+ lambda{CollectionDoNotSetABasis.on_watched_store_save_event("store", "saved_object")}.should raise_error
32
+ end
33
+
34
+ it "should complain when given a delete event" do
35
+ lambda{CollectionDoNotSetABasis.on_watched_store_delete_event("store", "deleted_object")}.should raise_error
36
+ end
37
+ end
38
+
39
+ context "(when a save event happens)" do
40
+ before(:each) do
41
+ CollectionFoo.basis = :basis
42
+ @store = HashPersistent::Store.new({}, "")
43
+ end
44
+
45
+ it "should create a resource for the basis if one does not exist" do
46
+ object = OpenStruct.new
47
+ object.key = "key"
48
+ object.basis = "basis"
49
+
50
+ CollectionFoo.on_watched_store_save_event(@store, object)
51
+ @store.find("basis").should_not be_nil
52
+ end
53
+
54
+ it "should collect the saved keys for a basis, in the resource for that basis" do
55
+ object = OpenStruct.new
56
+ object.basis = "basis"
57
+
58
+ object.key = "key_1"
59
+ CollectionFoo.on_watched_store_save_event(@store, object)
60
+
61
+ object.key = "key_2"
62
+ CollectionFoo.on_watched_store_save_event(@store, object)
63
+
64
+ collection_resource = @store.find("basis")
65
+ collection_resource.collected_keys.should include("key_1")
66
+ collection_resource.collected_keys.should include("key_2")
67
+ end
68
+
69
+ it "should not duplicate keys in the resource for the basis" do
70
+ object = OpenStruct.new
71
+ object.basis = "basis"
72
+
73
+ object.key = "key_1"
74
+ CollectionFoo.on_watched_store_save_event(@store, object)
75
+ CollectionFoo.on_watched_store_save_event(@store, object)
76
+
77
+ object.key = "key_2"
78
+ CollectionFoo.on_watched_store_save_event(@store, object)
79
+
80
+ collection_resource = @store.find("basis")
81
+ collection_resource.collected_keys.size.should == 2
82
+ end
83
+
84
+ it "should not cross-contaminate keys between bases" do
85
+ object = OpenStruct.new
86
+
87
+ object.basis = "basis_1"
88
+ object.key = "key_1A"
89
+ CollectionFoo.on_watched_store_save_event(@store, object)
90
+ object.key = "key_1B"
91
+ CollectionFoo.on_watched_store_save_event(@store, object)
92
+
93
+ object.basis = "basis_2"
94
+ object.key = "key_2"
95
+ CollectionFoo.on_watched_store_save_event(@store, object)
96
+
97
+ collection_resource_1 = @store.find("basis_1")
98
+ collection_resource_2 = @store.find("basis_2")
99
+
100
+ collection_resource_1.collected_keys.should include("key_1A")
101
+ collection_resource_1.collected_keys.should include("key_1B")
102
+ collection_resource_1.collected_keys.should_not include("key_2")
103
+
104
+ collection_resource_2.collected_keys.should_not include("key_1A")
105
+ collection_resource_2.collected_keys.should_not include("key_1B")
106
+ collection_resource_2.collected_keys.should include("key_2")
107
+ end
108
+ end
109
+
110
+ context "(when a delete event happens)" do
111
+ before(:each) do
112
+ CollectionFoo.basis = :basis
113
+ @store = HashPersistent::Store.new({}, "")
114
+ end
115
+
116
+ it "should not complain when asked to remove a key from an unknown basis" do
117
+ object = OpenStruct.new
118
+
119
+ object.basis = "basis"
120
+ object.key = "key"
121
+ lambda{CollectionFoo.on_watched_store_delete_event(@store, object)}.should_not raise_error
122
+ end
123
+
124
+ it "should delete the key from the resource for a basis" do
125
+ object = OpenStruct.new
126
+
127
+ object.basis = "basis"
128
+ object.key = "key_1"
129
+ CollectionFoo.on_watched_store_save_event(@store, object) # save
130
+ object.key = "key_2"
131
+ CollectionFoo.on_watched_store_save_event(@store, object) # save
132
+
133
+ object.key = "key_1"
134
+ CollectionFoo.on_watched_store_delete_event(@store, object) # delete
135
+
136
+ collection_resource = @store.find("basis")
137
+
138
+ collection_resource.collected_keys.should include("key_2")
139
+ collection_resource.collected_keys.should_not include("key_1")
140
+ end
141
+
142
+ it "should not complain when asked to remove a non-existent key from the basis" do
143
+ object = OpenStruct.new
144
+
145
+ object.basis = "basis"
146
+ object.key = "key_1"
147
+ CollectionFoo.on_watched_store_save_event(@store, object)
148
+
149
+ object.key = "key_2"
150
+ lambda{CollectionFoo.on_watched_store_delete_event(@store, object)}.should_not raise_error
151
+ end
152
+
153
+ it "should remove the resource for a basis when the last key is removed" do
154
+ object = OpenStruct.new
155
+
156
+ object.basis = "basis"
157
+ object.key = "key"
158
+ CollectionFoo.on_watched_store_save_event(@store, object)
159
+ CollectionFoo.on_watched_store_delete_event(@store, object)
160
+
161
+ @store.find("basis").should be_nil
162
+ end
163
+
164
+ it "should not complain when asked to remove a key from a deleted basis" do
165
+ object = OpenStruct.new
166
+
167
+ object.basis = "basis"
168
+ object.key = "key"
169
+ CollectionFoo.on_watched_store_save_event(@store, object)
170
+ CollectionFoo.on_watched_store_delete_event(@store, object)
171
+ lambda{CollectionFoo.on_watched_store_delete_event(@store, object)}.should_not raise_error
172
+ end
173
+ end
174
+
175
+ it "should do nothing when told about save or delete events for objects with no key attribute" do
176
+ store = HashPersistent::Store.new({}, "")
177
+ object = OpenStruct.new
178
+ object.basis = "fred"
179
+
180
+ CollectionFoo.on_watched_store_save_event(store, object)
181
+ store.key_value_store.should == {}
182
+ CollectionFoo.on_watched_store_delete_event(store, object)
183
+ store.key_value_store.should == {}
184
+ end
185
+
186
+ it "should do nothing when told about save or delete events for objects with no basis attribute" do
187
+ store = HashPersistent::Store.new({}, "")
188
+ object = OpenStruct.new
189
+ object.key = "fred"
190
+
191
+ CollectionFoo.on_watched_store_save_event(store, object)
192
+ store.key_value_store.should == {}
193
+ CollectionFoo.on_watched_store_delete_event(store, object)
194
+ store.key_value_store.should == {}
195
+ end
196
+
197
+ context "(dereferencing collected keys)" do
198
+ it "should provide a convenient means to retrieve/dereference collected resources" do
199
+ watched_store = HashPersistent::Store.new({}, "")
200
+ watching_store = HashPersistent::Store.new({}, "")
201
+ watching_store.watch(watched_store)
202
+ watching_store.managed_class = CollectionFoo
203
+ CollectionFoo.basis = :basis
204
+
205
+ object = OpenStruct.new
206
+
207
+ object.basis = "fred"
208
+ object.key = "1"
209
+ watched_store.save(object)
210
+
211
+ object.basis = "fred"
212
+ object.key = "2"
213
+ watched_store.save(object)
214
+
215
+ object.basis = "barney"
216
+ object.key = "3"
217
+ watched_store.save(object)
218
+
219
+ fred = watching_store.find("fred")
220
+ fred.collected_resources.should include(watched_store.find("1"))
221
+ fred.collected_resources.should include(watched_store.find("2"))
222
+ end
223
+
224
+ it "should complain if asked to dereference a resource that does not exist in the watched store" do
225
+ watched_store = HashPersistent::Store.new({}, "")
226
+ watching_store = HashPersistent::Store.new({}, "")
227
+ watching_store.managed_class = CollectionFoo
228
+ watching_store.watch(watched_store)
229
+ CollectionFoo.basis = :basis
230
+
231
+ object = OpenStruct.new
232
+ object.basis = "fred"
233
+ object.key = "1"
234
+ watched_store.save(object)
235
+
236
+ object.basis = "fred"
237
+ object.key = "2"
238
+ watched_store.save(object)
239
+
240
+ watched_store.watched_by = nil
241
+ watched_store.delete(object)
242
+ watched_store.watched_by = watching_store
243
+
244
+ fred = watching_store.find("fred")
245
+ lambda{fred.collected_resources}.should raise_error
246
+ end
247
+
248
+ it "should complain if asked to dereference a resource but does not know what store it's watching" do
249
+ watched_store = HashPersistent::Store.new({}, "")
250
+ watching_store = HashPersistent::Store.new({}, "")
251
+ watching_store.managed_class = CollectionFoo
252
+ watched_store.watched_by = watching_store # Note, only on-way relationship here
253
+ CollectionFoo.basis = :basis
254
+
255
+ object = OpenStruct.new
256
+ object.basis = "fred"
257
+ object.key = "1"
258
+ watched_store.save(object)
259
+
260
+ object.basis = "fred"
261
+ object.key = "2"
262
+ watched_store.save(object)
263
+
264
+ fred = watching_store.find("fred")
265
+ lambda{fred.collected_resources}.should raise_error
266
+ end
267
+ end
268
+ end
269
+
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "HashPersistent::Counter" do
4
+ context "(instantiation)" do
5
+ it "should expect to persist to a HashPersistent::Store and a string-like key" do
6
+ HashPersistent::Counter.new(HashPersistent::Store.new({}, ""), "key")
7
+ end
8
+
9
+ it "should reject an incorrect store type" do
10
+ lambda {HashPersistent::Counter.new(1, "key")}.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it "should reject a non-string-like key" do
14
+ lambda {HashPersistent::Counter.new(HashPersistent::Store.new({}, ""), Dummy_NoStringRep.new)}.should raise_error(ArgumentError)
15
+ end
16
+
17
+ it "should reject an empty string key" do
18
+ lambda {HashPersistent::Counter.new(HashPersistent::Store.new({}, ""), "")}.should raise_error(ArgumentError)
19
+ end
20
+
21
+ it "should add an count object to the store" do
22
+ store = HashPersistent::Store.new({}, "")
23
+ HashPersistent::Counter.new(store, "counter_name")
24
+ store.find("counter_name").count.should == 0
25
+ end
26
+
27
+ it "should not change the count object in the store if already present" do
28
+ store = HashPersistent::Store.new({}, "")
29
+ counter_1 = HashPersistent::Counter.new(store, "counter_name")
30
+ counter_1.next.should == 0
31
+ counter_1.next.should == 1
32
+ counter_1.next.should == 2
33
+
34
+ counter_2 = HashPersistent::Counter.new(store, "counter_name")
35
+ store.find("counter_name").count.should == 3
36
+ end
37
+ end
38
+
39
+ context "(generating unique ids)" do
40
+ it "should increment the count value in the store when a new id is requested" do
41
+ store = HashPersistent::Store.new({}, "")
42
+ counter = HashPersistent::Counter.new(store, "counter_name")
43
+ counter.next
44
+ store.find("counter_name").count.should == 1
45
+ counter.next
46
+ store.find("counter_name").count.should == 2
47
+ end
48
+
49
+ it "should return the count held in the store when a new id is requested" do
50
+ store = HashPersistent::Store.new({}, "")
51
+ counter = HashPersistent::Counter.new(store, "counter_name")
52
+ counter.next.should == 0
53
+ counter.next.should == 1
54
+ counter.next.should == 2
55
+ end
56
+ end
57
+
58
+ context "(concurrent access)" do
59
+ it "should correctly protect against use by mutliple threads/processes"
60
+ end
61
+ end
@@ -0,0 +1,103 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "HashPersistent::ExtendedStore" do
4
+ context "(instantiation)" do
5
+ it "should initialise its store attribute" do
6
+ store = HashPersistent::Store.new(Hash.new, "prefix::")
7
+ extended_store = HashPersistent::ExtendedStore.new(store)
8
+ extended_store.store.should == store
9
+ end
10
+ end
11
+
12
+ context "(in default configuration)" do
13
+ it "should delegate the find method to its store" do
14
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
15
+ extended_store.store.should_receive(:find).with("a_key")
16
+ extended_store.find("a_key")
17
+ end
18
+
19
+ it "should delegate the save method to its store" do
20
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
21
+ object = OpenStruct.new
22
+ object.key = "a_key"
23
+ extended_store.store.should_receive(:save).with(object)
24
+ extended_store.save(object)
25
+ end
26
+
27
+ it "should delegate the delete method to its store" do
28
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
29
+ object = OpenStruct.new
30
+ object.key = "a_key"
31
+ extended_store.store.should_receive(:delete).with(object)
32
+ extended_store.delete(object)
33
+ end
34
+
35
+ it "should delegate the create method to its store" do
36
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
37
+ extended_store.store.should_receive(:create).with("key", OpenStruct)
38
+ extended_store.create("key", OpenStruct)
39
+ end
40
+
41
+ it "should delegate the find_or_create method to its store" do
42
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
43
+ extended_store.store.should_receive(:find_or_create).with("key", OpenStruct)
44
+ extended_store.find_or_create("key", OpenStruct)
45
+ end
46
+ end
47
+
48
+ context "(singleton keys)" do
49
+ it "should allow a singleton key pattern to be specified" do
50
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
51
+ extended_store.singleton_key("foo", OpenStruct)
52
+
53
+ HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, "")) do |store|
54
+ store.singleton_key "foo", OpenStruct
55
+ end
56
+ end
57
+
58
+ it "should reject a pattern that cannot be matched against a string" do
59
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
60
+ lambda{extended_store.singleton_key(1, OpenStruct)}.should raise_error
61
+ end
62
+
63
+ it "should not redirect find for keys that do not match the singleton pattern" do
64
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
65
+ extended_store.singleton_key("foo", OpenStruct)
66
+
67
+ extended_store.store.should_receive(:find).with("bar")
68
+ extended_store.store.should_not_receive(:find_or_create)
69
+
70
+ extended_store.find("bar")
71
+ end
72
+
73
+ it "should redirect find to find_or_create for keys that match the singleton pattern" do
74
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
75
+ extended_store.singleton_key("foo", OpenStruct, "arg1", "arg2")
76
+
77
+ extended_store.store.should_receive(:find_or_create).with("food", OpenStruct, "arg1", "arg2")
78
+ extended_store.store.should_not_receive(:find)
79
+
80
+ extended_store.find("food")
81
+ end
82
+
83
+ it "should allow multiple patterns to be specified" do
84
+ extended_store = HashPersistent::ExtendedStore.new(HashPersistent::Store.new({}, ""))
85
+ extended_store.singleton_key("foo", OpenStruct, "arg1", "arg2")
86
+ extended_store.singleton_key("bar", Object, "arg3", "arg4")
87
+
88
+ extended_store.store.should_receive(:find_or_create).with("food", OpenStruct, "arg1", "arg2")
89
+ extended_store.find("food")
90
+
91
+ extended_store.store.should_receive(:find_or_create).with("barney", Object, "arg3", "arg4")
92
+ extended_store.find("barney")
93
+ end
94
+ end
95
+
96
+ context "(proxy stores)" do
97
+ it "should be clever"
98
+ end
99
+
100
+ context "(watching objects)" do
101
+ it "should be clever"
102
+ end
103
+ end
@@ -0,0 +1,48 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class ResourceFoo
4
+ include HashPersistent::Resource
5
+ end
6
+
7
+
8
+ describe "A class that includes HashPersistent::Resource" do
9
+ it "should acquire a store attribute" do
10
+ resource = ResourceFoo.new
11
+ resource.store = "foo"
12
+ resource.store.should == "foo"
13
+ end
14
+
15
+ it "should acquire a key attribute" do
16
+ resource = ResourceFoo.new
17
+ resource.key = "foo"
18
+ resource.key.should == "foo"
19
+ end
20
+
21
+ it "should be savable to its store" do
22
+ resource = ResourceFoo.new
23
+ mock_store = "dummy"
24
+ resource.store = mock_store
25
+
26
+ mock_store.should_receive(:save).with(resource)
27
+ resource.save
28
+ end
29
+
30
+ it "should be deletable from its store" do
31
+ resource = ResourceFoo.new
32
+ mock_store = "dummy"
33
+ resource.store = mock_store
34
+
35
+ mock_store.should_receive(:delete).with(resource)
36
+ resource.delete
37
+ end
38
+
39
+ it "should be movable from one store to another" do
40
+ resource = ResourceFoo.new
41
+ mock_store = "dummy" # We use only one so that we can use rspec to check the order of the messages...
42
+ resource.store = mock_store
43
+
44
+ mock_store.should_receive(:delete).with(resource).ordered
45
+ mock_store.should_receive(:save).with(resource).ordered
46
+ resource.move(mock_store)
47
+ end
48
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format nested
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'ostruct'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ require 'hash-persistent'
8
+
9
+ class Dummy_NoStringRep
10
+ undef to_s
11
+ end
12
+
13
+ class Dummy_RestrictedHash < Hash
14
+ undef each, each_key, each_pair, each_value
15
+ #TODO: expand this list, or just add dependency one of the moneta implementations?
16
+ end
17
+
18
+ Spec::Runner.configure do |config|
19
+
20
+ end
@@ -0,0 +1,370 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class ClassWithKey
4
+ attr_accessor :key, :var
5
+ def initialize(key, var)
6
+ @key = key
7
+ @var = var
8
+ end
9
+
10
+ def ==(other)
11
+ (other.class == ClassWithKey) && (key == other.key) && (var == other.var)
12
+ end
13
+ end
14
+
15
+ class ClassWithKeyAndStore
16
+ attr_accessor :key, :store, :var
17
+
18
+ def initialize(key = nil)
19
+ @key = key
20
+ end
21
+ end
22
+
23
+ describe "HashPersistent::Store" do
24
+ context "(instantiation)" do
25
+ it "should expect to persist to a hash-like key_value_store, with a string-like key_prefix" do
26
+ HashPersistent::Store.new({}, "_prefix")
27
+ end
28
+
29
+ it "should reject a non-hash-like key_value_store" do
30
+ lambda {HashPersistent::Store.new(1, "_prefix")}.should raise_error(ArgumentError)
31
+ end
32
+
33
+ it "should reject a non-string-like key_prefix" do
34
+ lambda {HashPersistent::Store.new({}, Dummy_NoStringRep.new)}.should raise_error(ArgumentError)
35
+ end
36
+
37
+ it "should maintain a key_value_store attribute" do
38
+ a_hash = {}
39
+ HashPersistent::Store.new(a_hash, "").key_value_store.should == a_hash
40
+ end
41
+
42
+ it "should maintain a key_prefix attribute" do
43
+ HashPersistent::Store.new({}, "foo_bar").key_prefix.should == "foo_bar"
44
+ end
45
+ end
46
+
47
+ context "(saving)" do
48
+ it "should refuse to save an object that does not provide a non-nil key" do
49
+ lambda{HashPersistent::Store.new({}, "").save(1)}.should raise_error
50
+ object = ClassWithKey.new(nil, "foo")
51
+ lambda{HashPersistent::Store.new({}, "").save(object)}.should raise_error
52
+ end
53
+
54
+ it "should add the object, referenced by its prefixed key, to the store" do
55
+ store = HashPersistent::Store.new({}, "prefix::")
56
+ object = ClassWithKey.new("foo", "bar")
57
+ store.save(object).key_value_store["prefix::foo"].should == object
58
+ end
59
+
60
+ it "should overwrite an object with same prefixed key in the store" do
61
+ store = HashPersistent::Store.new({}, "prefix::")
62
+ object_1 = ClassWithKey.new("foo", "bar")
63
+ object_2 = ClassWithKey.new("foo", "baz")
64
+ store.save(object_1).save(object_2).key_value_store["prefix::foo"].should == object_2
65
+ end
66
+ end
67
+
68
+ context "(deleting)" do
69
+ it "should refuse to delete an object that does not provide a non-nil key" do
70
+ lambda{HashPersistent::Store.new({}, "").delete(1)}.should raise_error
71
+ object = ClassWithKey.new(nil, "foo")
72
+ lambda{HashPersistent::Store.new({}, "").delete(object)}.should raise_error
73
+ end
74
+
75
+ it "should delete a pair from the store based on the object's prefixed key" do
76
+ store = HashPersistent::Store.new({}, "prefix::")
77
+ object = ClassWithKey.new("foo", "bar")
78
+ store.save(object).delete(object).key_value_store["prefix::foo"].should be_nil
79
+ end
80
+
81
+ it "should not complain if the prefixed key does not exist in the store" do
82
+ store = HashPersistent::Store.new({}, "prefix::")
83
+ object = ClassWithKey.new("foo", "bar")
84
+ lambda{store.delete(object)}.should_not raise_error
85
+ end
86
+ end
87
+
88
+ context "(finding)" do
89
+ it "should retrieve an object that has the correct key" do
90
+ store = HashPersistent::Store.new({}, "prefix::")
91
+ object = ClassWithKey.new("foo", "bar")
92
+ store.save(object).find("foo").should == object
93
+ end
94
+
95
+ it "should return nil if no object is found with the correct key" do
96
+ store = HashPersistent::Store.new({}, "prefix::")
97
+ object = ClassWithKey.new("foo", "bar")
98
+ store.find("foo").should be_nil
99
+ store.save(object).delete(object).find("foo").should be_nil
100
+ end
101
+ end
102
+
103
+ context "(creating)" do
104
+ it "should create objects on request" do
105
+ new_object = ClassWithKeyAndStore.new
106
+ ClassWithKeyAndStore.should_receive(:new).with("arg1", "arg2").and_return(new_object)
107
+ object = HashPersistent::Store.new({}, "prefix::").create("a_key", ClassWithKeyAndStore, "arg1", "arg2")
108
+ object.should == new_object
109
+ end
110
+
111
+ it "should set the key attribute of created objects" do
112
+ block = lambda{"foo"}
113
+ object = HashPersistent::Store.new({}, "prefix::").create("a_key", ClassWithKeyAndStore)
114
+ object.key.should == "a_key"
115
+ end
116
+
117
+ it "should be able to create and save when the requested key is not found" do
118
+ store = HashPersistent::Store.new({}, "")
119
+ store.find_or_create("a_key", OpenStruct, {:foo => "bar"})
120
+ store.find("a_key").should_not be_nil
121
+ store.find("a_key").foo.should == "bar"
122
+ end
123
+
124
+ it "should not create or modify when the requested key is found" do
125
+ store = HashPersistent::Store.new({}, "")
126
+ store.save(store.create("a_key", OpenStruct, {:foo => "bar"}))
127
+
128
+ store.find_or_create("a_key", OpenStruct, {:foo => "baz", :fred => "barney"})
129
+
130
+ store.find("a_key").should_not be_nil
131
+ store.find("a_key").fred.should be_nil
132
+ store.find("a_key").foo.should == "bar"
133
+ end
134
+ end
135
+
136
+ context "(isolation of objects in in-memory stores)" do
137
+ it "should return a copy of the object found in the store" do
138
+ store = HashPersistent::Store.new({}, "")
139
+ object = ClassWithKey.new("foo", "bar")
140
+ store.save(object).find("foo").should_not equal(store.key_value_store["foo"])
141
+ end
142
+
143
+ it "should save a copy of the object being saved" do
144
+ store = HashPersistent::Store.new({}, "")
145
+ object = ClassWithKey.new("foo", "bar")
146
+ store.save(object)
147
+ store.key_value_store["foo"].should_not equal(object)
148
+ end
149
+ end
150
+
151
+ context "(handling objects that expose a store attribute)" do
152
+ it "should set the store attribute of a created object" do
153
+ store = HashPersistent::Store.new({}, "prefix::")
154
+ store.create("key", ClassWithKeyAndStore).store.should == store
155
+ end
156
+
157
+ it "should set the store attribute of a saved object" do
158
+ object = ClassWithKeyAndStore.new("foo")
159
+ store = HashPersistent::Store.new({}, "")
160
+ store.save(object)
161
+ object.store.should == store
162
+ end
163
+
164
+ it "should not save the store attribute into the store" do
165
+ object = ClassWithKeyAndStore.new("foo")
166
+ object.store = "faa"
167
+
168
+ store = HashPersistent::Store.new({}, "")
169
+ store.save(object)
170
+
171
+ store.key_value_store["foo"].store.should be_nil
172
+ end
173
+
174
+ it "should set the store attribute of a found object" do
175
+ object = ClassWithKeyAndStore.new("foo")
176
+ object.store = "faa"
177
+
178
+ store = HashPersistent::Store.new({}, "")
179
+ store.save(object).find("foo").store.should == store
180
+ end
181
+
182
+ it "leave the in-store object's store attribute nil after finding/returning the object" do
183
+ object = ClassWithKeyAndStore.new("foo")
184
+ object.store = "faa"
185
+
186
+ store = HashPersistent::Store.new({}, "")
187
+ store.save(object).find("foo")
188
+ store.key_value_store["foo"].store.should be_nil
189
+ end
190
+
191
+ it "set the object's store attribute after deleting the object" do
192
+ store = HashPersistent::Store.new({}, "")
193
+ object = ClassWithKeyAndStore.new("foo")
194
+
195
+ store.save(object)
196
+ object.store = nil
197
+ store.delete(object)
198
+ object.store.should == store
199
+ end
200
+ end
201
+
202
+ context "(use of restricted Hash API)" do
203
+ it "should not require Hash methods that are not implemented in Moneta"
204
+ end
205
+
206
+ context "(caching)" do
207
+ it "should accept a HashPersistent::Store cache" do
208
+ cache = HashPersistent::Store.new({}, "")
209
+ store = HashPersistent::Store.new({}, "") do |the_store|
210
+ the_store.cached_to = cache
211
+ end
212
+ store.cached_to.should == cache
213
+ end
214
+
215
+ # TODO: can relax this?
216
+ # We'd like to reproduce some nice behaviours in the cache (like unsetting object.store on saving)
217
+ it "should reject a cache of the wrong type" do
218
+ store = HashPersistent::Store.new({}, "")
219
+ lambda{store.cached_to = {}}.should raise_error(ArgumentError)
220
+ end
221
+
222
+ it "should allow the cache to be unset using nil" do
223
+ store = HashPersistent::Store.new({}, "")
224
+ store.cached_to = nil
225
+ store.cached_to.should == nil
226
+ end
227
+
228
+ context "if supplied a cache" do
229
+ it "should also save an object to the cache when saving" do
230
+ cache = HashPersistent::Store.new({}, "")
231
+ store = HashPersistent::Store.new({}, "")
232
+ store.cached_to = cache
233
+
234
+ object = ClassWithKeyAndStore.new("foo")
235
+ cache.should_receive(:save) do |the_save_object|
236
+ the_save_object.key.should == "foo"
237
+ end
238
+ store.save(object)
239
+ end
240
+
241
+ it "should also delete an object from the cache when deleting" do
242
+ cache = HashPersistent::Store.new({}, "")
243
+ store = HashPersistent::Store.new({}, "")
244
+ store.cached_to = cache
245
+
246
+ object = ClassWithKeyAndStore.new("foo")
247
+ store.save(object)
248
+
249
+ cache.should_receive(:delete) do |the_delete_object|
250
+ the_delete_object.key.should == "foo"
251
+ end
252
+ store.delete(object)
253
+ end
254
+
255
+ it "should save an object to the cache if it is not present on find()" do
256
+ cache = HashPersistent::Store.new({}, "")
257
+ store = HashPersistent::Store.new({}, "")
258
+
259
+ object = ClassWithKeyAndStore.new("foo")
260
+ store.save(object)
261
+
262
+ # We're making an assumption here that no data is copied to the cache when we do this
263
+ store.cached_to = cache
264
+
265
+ cache.should_receive(:save) do |the_save_object|
266
+ the_save_object.key.should == "foo"
267
+ end
268
+ store.find("foo")
269
+ end
270
+
271
+ it "should return the object from the cache if it is present on find()" do
272
+ cache = HashPersistent::Store.new({}, "")
273
+ store = HashPersistent::Store.new({}, "")
274
+ store.cached_to = cache
275
+
276
+ object = ClassWithKeyAndStore.new("foo")
277
+ object.var = "bar"
278
+ store.save(object)
279
+
280
+ object.var = "baz"
281
+ cache.save(object)
282
+
283
+ store.find("foo").var.should == "baz"
284
+ end
285
+
286
+ it "should not hit the store if the object is present in the cache on find()" do
287
+ cache = HashPersistent::Store.new({}, "")
288
+ store = HashPersistent::Store.new({}, "")
289
+ store.cached_to = cache
290
+
291
+ object = ClassWithKeyAndStore.new("foo")
292
+ store.save(object)
293
+
294
+ # We assume that Fixnum is missing methods required in find(), but we verify this assumption below
295
+ class HashPersistent::Store
296
+ def hack_key_value_store
297
+ @key_value_store = 1
298
+ end
299
+ end
300
+
301
+ store.hack_key_value_store
302
+
303
+ lambda{store.find("foo")}.should_not raise_error
304
+
305
+ store.cached_to = nil
306
+ lambda{store.find("foo")}.should raise_error
307
+
308
+ class HashPersistent::Store
309
+ undef hack_key_value_store
310
+ end
311
+ end
312
+
313
+ it "should correctly set the object's store attribute when saving" do
314
+ cache = HashPersistent::Store.new({}, "")
315
+ store = HashPersistent::Store.new({}, "")
316
+ store.cached_to = cache
317
+
318
+ object = ClassWithKeyAndStore.new("foo")
319
+ store.save(object)
320
+ object.store.should == store
321
+ end
322
+
323
+ it "should correctly set the object's store attribute when deleting" do
324
+ cache = HashPersistent::Store.new({}, "")
325
+ store = HashPersistent::Store.new({}, "")
326
+ store.cached_to = cache
327
+
328
+ object = ClassWithKeyAndStore.new("foo")
329
+
330
+ store.save(object)
331
+ object.store = nil
332
+
333
+ store.delete(object)
334
+ object.store.should == store
335
+ end
336
+
337
+ it "should correctly set the object's store attribute when finding/caching" do
338
+ cache = HashPersistent::Store.new({}, "")
339
+ store = HashPersistent::Store.new({}, "")
340
+
341
+ object = ClassWithKeyAndStore.new("foo")
342
+ store.save(object)
343
+
344
+ store.cached_to = cache
345
+ found = store.find("foo")
346
+
347
+ found.store.should == store
348
+ end
349
+
350
+ it "should correctly set the object's store attribute when finding from the cache" do
351
+ cache = HashPersistent::Store.new({}, "")
352
+ store = HashPersistent::Store.new({}, "")
353
+ store.cached_to = cache
354
+
355
+ object = ClassWithKeyAndStore.new("foo")
356
+ store.save(object)
357
+
358
+ store.find("foo").store.should == store
359
+ end
360
+ end
361
+ end
362
+
363
+ context "(consistency of store, watched store and cache after errors occur)" do
364
+ it "should do something both boring and predictable"
365
+ end
366
+
367
+ context "(concurrent access)" do
368
+ it "should use locks and such to handle access from multiple threads/processes"
369
+ end
370
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kissifer-hash-persistent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - kissifer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-23 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: tierneydrchris@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION
32
+ - hash-persistent.gemspec
33
+ - lib/hash-persistent.rb
34
+ - lib/hash-persistent/collection.rb
35
+ - lib/hash-persistent/counter.rb
36
+ - lib/hash-persistent/extended_store.rb
37
+ - lib/hash-persistent/resource.rb
38
+ - lib/hash-persistent/store.rb
39
+ - spec/collection_spec.rb
40
+ - spec/counter_spec.rb
41
+ - spec/extended_store_spec.rb
42
+ - spec/resource_spec.rb
43
+ - spec/spec.opts
44
+ - spec/spec_helper.rb
45
+ - spec/store_spec.rb
46
+ has_rdoc: false
47
+ homepage: http://github.com/kissifer/hash-persistent
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Library of base classes to simplify persisting objects and collections to a moneta store
72
+ test_files:
73
+ - spec/collection_spec.rb
74
+ - spec/counter_spec.rb
75
+ - spec/extended_store_spec.rb
76
+ - spec/resource_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/store_spec.rb