kissifer-hash-persistent 0.0.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/.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