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 +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/hash-persistent.gemspec +60 -0
- data/lib/hash-persistent/collection.rb +59 -0
- data/lib/hash-persistent/counter.rb +26 -0
- data/lib/hash-persistent/extended_store.rb +49 -0
- data/lib/hash-persistent/resource.rb +20 -0
- data/lib/hash-persistent/store.rb +67 -0
- data/lib/hash-persistent.rb +8 -0
- data/spec/collection_spec.rb +269 -0
- data/spec/counter_spec.rb +61 -0
- data/spec/extended_store_spec.rb +103 -0
- data/spec/resource_spec.rb +48 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/store_spec.rb +370 -0
- metadata +78 -0
data/.document
ADDED
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
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,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
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/spec/store_spec.rb
ADDED
@@ -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
|