perobs 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/Rakefile +2 -4
- data/lib/perobs/Array.rb +58 -108
- data/lib/perobs/BTreeDB.rb +1 -1
- data/lib/perobs/Cache.rb +12 -0
- data/lib/perobs/Delegator.rb +78 -0
- data/lib/perobs/DynamoDB.rb +1 -1
- data/lib/perobs/Hash.rb +56 -110
- data/lib/perobs/Object.rb +35 -19
- data/lib/perobs/ObjectBase.rb +82 -28
- data/lib/perobs/Store.rb +75 -9
- data/lib/perobs/version.rb +1 -1
- data/tasks/test.rake +2 -1
- data/test/Array_spec.rb +208 -0
- data/{spec → test}/BTreeDB_spec.rb +23 -23
- data/{spec → test}/ClassMap_spec.rb +15 -15
- data/test/Hash_spec.rb +157 -0
- data/{spec → test}/Object_spec.rb +53 -28
- data/{spec → test}/Store_spec.rb +135 -129
- data/{spec → test}/perobs_spec.rb +44 -47
- data/{spec/Array_spec.rb → test/spec_helper.rb} +9 -61
- metadata +29 -26
- data/spec/Hash_spec.rb +0 -96
data/lib/perobs/Object.rb
CHANGED
@@ -76,7 +76,9 @@ module PEROBS
|
|
76
76
|
|
77
77
|
attr_reader :attributes
|
78
78
|
|
79
|
-
#
|
79
|
+
# New PEROBS objects must always be created by calling # Store.new().
|
80
|
+
# PEROBS users should never call this method or equivalents of derived
|
81
|
+
# methods directly.
|
80
82
|
def initialize(store)
|
81
83
|
super
|
82
84
|
end
|
@@ -89,7 +91,7 @@ module PEROBS
|
|
89
91
|
# @param val [Any] Value to be set
|
90
92
|
# @return [true|false] True if the value was initialized, otherwise false.
|
91
93
|
def init_attr(attr, val)
|
92
|
-
if
|
94
|
+
if _all_attributes.include?(attr)
|
93
95
|
_set(attr, val)
|
94
96
|
return true
|
95
97
|
end
|
@@ -102,9 +104,9 @@ module PEROBS
|
|
102
104
|
# @return [Array of Fixnum or Bignum] IDs of referenced objects
|
103
105
|
def _referenced_object_ids
|
104
106
|
ids = []
|
105
|
-
|
107
|
+
_all_attributes.each do |attr|
|
106
108
|
value = instance_variable_get(('@' + attr.to_s).to_sym)
|
107
|
-
ids << value.id if value && value.
|
109
|
+
ids << value.id if value && value.respond_to?(:is_poxreference?)
|
108
110
|
end
|
109
111
|
ids
|
110
112
|
end
|
@@ -113,10 +115,10 @@ module PEROBS
|
|
113
115
|
# delete all referenced to the given object ID.
|
114
116
|
# @param id [Fixnum/Bignum] targeted object ID
|
115
117
|
def _delete_reference_to_id(id)
|
116
|
-
|
118
|
+
_all_attributes.each do |attr|
|
117
119
|
ivar = ('@' + attr.to_s).to_sym
|
118
120
|
value = instance_variable_get(ivar)
|
119
|
-
if value && value.
|
121
|
+
if value && value.respond_to?(:is_poxreference?) && value.id == id
|
120
122
|
instance_variable_set(ivar, nil)
|
121
123
|
end
|
122
124
|
end
|
@@ -129,6 +131,7 @@ module PEROBS
|
|
129
131
|
def _deserialize(data)
|
130
132
|
# Initialize all attributes with the provided values.
|
131
133
|
data.each do |attr_name, value|
|
134
|
+
value = POXReference.new(@store, value.id) if value.is_a?(POReference)
|
132
135
|
instance_variable_set(('@' + attr_name).to_sym, value)
|
133
136
|
end
|
134
137
|
end
|
@@ -137,7 +140,7 @@ module PEROBS
|
|
137
140
|
# @return [String]
|
138
141
|
def inspect
|
139
142
|
"{\n" +
|
140
|
-
|
143
|
+
_all_attributes.map do |attr|
|
141
144
|
ivar = ('@' + attr.to_s).to_sym
|
142
145
|
" #{attr.inspect}=>#{instance_variable_get(ivar).inspect}"
|
143
146
|
end.join(",\n") +
|
@@ -150,23 +153,25 @@ module PEROBS
|
|
150
153
|
# class.
|
151
154
|
def _serialize
|
152
155
|
attributes = {}
|
153
|
-
|
156
|
+
_all_attributes.each do |attr|
|
154
157
|
ivar = ('@' + attr.to_s).to_sym
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
158
|
+
value = instance_variable_get(ivar)
|
159
|
+
#if (value = instance_variable_get(ivar)).is_a?(ObjectBase)
|
160
|
+
# raise ArgumentError, "The instance variable #{ivar} contains a " +
|
161
|
+
# "reference to a PEROBS::ObjectBase object! " +
|
162
|
+
# "This is not allowed. You must use the " +
|
163
|
+
# "accessor method to assign a reference to " +
|
164
|
+
# "another PEROBS object."
|
165
|
+
#end
|
166
|
+
attributes[attr.to_s] = value.respond_to?(:is_poxreference?) ?
|
167
|
+
POReference.new(value.id) : value
|
163
168
|
end
|
164
169
|
attributes
|
165
170
|
end
|
166
171
|
|
167
172
|
def _set(attr, val)
|
168
173
|
ivar = ('@' + attr.to_s).to_sym
|
169
|
-
if val.is_a?(ObjectBase)
|
174
|
+
if !val.respond_to?(:is_poxreference?) && val.is_a?(ObjectBase)
|
170
175
|
# References to other PEROBS::Objects must be handled somewhat
|
171
176
|
# special.
|
172
177
|
if @store != val.store
|
@@ -175,7 +180,7 @@ module PEROBS
|
|
175
180
|
# To release the object from the Ruby object list later, we store the
|
176
181
|
# PEROBS::Store ID of the referenced object instead of the actual
|
177
182
|
# reference.
|
178
|
-
instance_variable_set(ivar,
|
183
|
+
instance_variable_set(ivar, POXReference.new(@store, val._id))
|
179
184
|
else
|
180
185
|
instance_variable_set(ivar, val)
|
181
186
|
end
|
@@ -187,13 +192,24 @@ module PEROBS
|
|
187
192
|
|
188
193
|
def _get(attr)
|
189
194
|
value = instance_variable_get(('@' + attr.to_s).to_sym)
|
190
|
-
if value.
|
195
|
+
if value.respond_to?(:is_poxreference?)
|
191
196
|
@store.object_by_id(value.id)
|
192
197
|
else
|
193
198
|
value
|
194
199
|
end
|
195
200
|
end
|
196
201
|
|
202
|
+
def _all_attributes
|
203
|
+
# PEROBS objects that don't have persistent attributes declared don't
|
204
|
+
# really make sense.
|
205
|
+
unless self.class.attributes
|
206
|
+
raise StandardError
|
207
|
+
"No persistent attributes have been declared for " +
|
208
|
+
"class #{self.class}. Use 'po_attr' to declare them."
|
209
|
+
end
|
210
|
+
self.class.attributes
|
211
|
+
end
|
212
|
+
|
197
213
|
end
|
198
214
|
|
199
215
|
end
|
data/lib/perobs/ObjectBase.rb
CHANGED
@@ -32,26 +32,88 @@ module PEROBS
|
|
32
32
|
# This class is used to replace a direct reference to another Ruby object by
|
33
33
|
# the Store ID. This makes object disposable by the Ruby garbage collector
|
34
34
|
# since it's no longer referenced once it has been evicted from the
|
35
|
-
# PEROBS::Store cache.
|
36
|
-
|
35
|
+
# PEROBS::Store cache. The POXReference objects function as a transparent
|
36
|
+
# proxy for the objects they are referencing.
|
37
|
+
class POXReference < BasicObject
|
38
|
+
|
39
|
+
attr_reader :store, :id
|
40
|
+
|
41
|
+
def initialize(store, id)
|
42
|
+
super()
|
43
|
+
@store = store
|
44
|
+
@id = id
|
45
|
+
end
|
46
|
+
|
47
|
+
# Proxy all calls to unknown methods to the referenced object.
|
48
|
+
def method_missing(method_sym, *args, &block)
|
49
|
+
unless (obj = _referenced_object)
|
50
|
+
raise ::RuntimeError, "Internal consistency error. No object with " +
|
51
|
+
"ID #{@id} found in the store"
|
52
|
+
end
|
53
|
+
if obj.respond_to?(:is_poxreference?)
|
54
|
+
raise ::RuntimeError,
|
55
|
+
"POXReference that references a POXReference found"
|
56
|
+
end
|
57
|
+
obj.send(method_sym, *args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Proxy all calls to unknown methods to the referenced object. Calling
|
61
|
+
# respond_to?(:is_poxreference?) is the only reliable way to find out if
|
62
|
+
# the object is a POXReference or not as pretty much every method call is
|
63
|
+
# proxied to the referenced object.
|
64
|
+
def respond_to?(method_sym, include_private = false)
|
65
|
+
(method_sym == :is_poxreference?) ||
|
66
|
+
_referenced_object.respond_to?(method_sym, include_private)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Just for completeness. We don't want to be caught lying.
|
70
|
+
def is_poxreference?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [ObjectBase] Return the referenced object. This method should
|
75
|
+
# not be used outside of the PEROBS library. Leaked references can cause
|
76
|
+
# data corruption.
|
77
|
+
def _referenced_object
|
78
|
+
@store.object_by_id(@id)
|
79
|
+
end
|
80
|
+
|
81
|
+
# BasicObject provides a ==() method that prevents method_missing from
|
82
|
+
# being called. So we have to pass the call manually to the referenced
|
83
|
+
# object.
|
84
|
+
# @param obj object to compare this object with.
|
85
|
+
def ==(obj)
|
86
|
+
_referenced_object == obj
|
87
|
+
end
|
37
88
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
"@#{id}"
|
89
|
+
# Shortcut to access the _id() method of the referenced object.
|
90
|
+
def _id
|
91
|
+
@id
|
42
92
|
end
|
43
93
|
|
44
94
|
end
|
45
95
|
|
96
|
+
# This class is used to serialize the POXReference objects. It only holds
|
97
|
+
# the ID of the referenced Object.
|
98
|
+
class POReference < Struct.new(:id)
|
99
|
+
end
|
100
|
+
|
46
101
|
# Base class for all persistent objects. It provides the functionality
|
47
102
|
# common to all classes of persistent objects.
|
48
103
|
class ObjectBase
|
49
104
|
|
50
105
|
attr_reader :_id, :store
|
51
106
|
|
52
|
-
#
|
107
|
+
# New PEROBS objects must always be created by calling # Store.new().
|
108
|
+
# PEROBS users should never call this method or equivalents of derived
|
109
|
+
# methods directly.
|
53
110
|
def initialize(store)
|
54
111
|
@store = store
|
112
|
+
unless @store.object_creation_in_progress
|
113
|
+
raise ::RuntimeError,
|
114
|
+
"All PEROBS objects must exclusively be created by calling " +
|
115
|
+
"Store.new(). Never call the object constructor directly."
|
116
|
+
end
|
55
117
|
@_id = @store.db.new_id
|
56
118
|
@_stash_map = nil
|
57
119
|
|
@@ -59,8 +121,18 @@ module PEROBS
|
|
59
121
|
@store.cache.cache_write(self)
|
60
122
|
end
|
61
123
|
|
124
|
+
public
|
125
|
+
|
126
|
+
# This method can be overloaded by derived classes to do some massaging on
|
127
|
+
# the data after it has been restored from the database. This could either
|
128
|
+
# be some sanity check or code to migrate the object from one version to
|
129
|
+
# another.
|
130
|
+
def post_restore
|
131
|
+
end
|
132
|
+
|
62
133
|
# Two objects are considered equal if their object IDs are the same.
|
63
134
|
def ==(obj)
|
135
|
+
return false unless obj.is_a?(ObjectBase)
|
64
136
|
obj && @_id == obj._id
|
65
137
|
end
|
66
138
|
|
@@ -85,11 +157,12 @@ module PEROBS
|
|
85
157
|
|
86
158
|
klass = store.class_map.id_to_class(db_obj['class_id'])
|
87
159
|
# Call the constructor of the specified class.
|
88
|
-
obj = Object.const_get(klass)
|
160
|
+
obj = store.construct_po(Object.const_get(klass))
|
89
161
|
# The object gets created with a new ID by default. We need to restore
|
90
162
|
# the old one.
|
91
163
|
obj._change_id(id)
|
92
164
|
obj._deserialize(db_obj['data'])
|
165
|
+
obj.post_restore
|
93
166
|
|
94
167
|
obj
|
95
168
|
end
|
@@ -140,29 +213,10 @@ module PEROBS
|
|
140
213
|
def _change_id(id)
|
141
214
|
# Unregister the object with the old ID from the write cache to prevent
|
142
215
|
# cache corruption. The objects are index by ID in the cache.
|
143
|
-
store.cache.unwrite(self)
|
216
|
+
@store.cache.unwrite(self)
|
144
217
|
@_id = id
|
145
218
|
end
|
146
219
|
|
147
|
-
private
|
148
|
-
|
149
|
-
def _dereferenced(v)
|
150
|
-
v.is_a?(POReference) ? @store.object_by_id(v.id) : v
|
151
|
-
end
|
152
|
-
|
153
|
-
def _referenced(obj)
|
154
|
-
if obj.is_a?(ObjectBase)
|
155
|
-
# The obj is a reference to another persistent object. Store the ID
|
156
|
-
# of that object in a POReference object.
|
157
|
-
if @store != obj.store
|
158
|
-
raise ArgumentError, 'The referenced object is not part of this store'
|
159
|
-
end
|
160
|
-
POReference.new(obj._id)
|
161
|
-
else
|
162
|
-
obj
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
220
|
end
|
167
221
|
|
168
222
|
end
|
data/lib/perobs/Store.rb
CHANGED
@@ -25,6 +25,8 @@
|
|
25
25
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
26
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
27
|
|
28
|
+
require 'set'
|
29
|
+
|
28
30
|
require 'perobs/Cache'
|
29
31
|
require 'perobs/ClassMap'
|
30
32
|
require 'perobs/BTreeDB'
|
@@ -38,18 +40,55 @@ module PEROBS
|
|
38
40
|
# PEROBS::Store is a persistent storage system for Ruby objects. Regular
|
39
41
|
# Ruby objects are transparently stored in a back-end storage and retrieved
|
40
42
|
# when needed. It features a garbage collector that removes all objects that
|
41
|
-
# are no longer in use.
|
43
|
+
# are no longer in use. A build-in cache keeps access latencies to recently
|
42
44
|
# used objects low and lazily flushes modified objects into the persistend
|
43
|
-
# back-end.
|
44
|
-
#
|
45
|
+
# back-end. The default back-end is a filesystem based database.
|
46
|
+
# Alternatively, an Amazon DynamoDB can be used as well. Adding support for
|
47
|
+
# other key/value stores is fairly trivial to do. See PEROBS::DynamoDB for
|
48
|
+
# an example
|
49
|
+
#
|
50
|
+
# Persistent objects must be defined by deriving your class from
|
51
|
+
# PEROBS::Object, PERBOS::Array or PEROBS::Hash. Only instance variables
|
52
|
+
# that are declared via po_attr will be persistent. It is recommended that
|
53
|
+
# references to other objects are all going to persistent objects again. TO
|
54
|
+
# create a new persistent object you must call Store.new(). Don't use the
|
55
|
+
# constructors of persistent classes directly. Store.new() will return a
|
56
|
+
# proxy or delegator object that can be used like the actual object. By
|
57
|
+
# using delegators we can disconnect the actual object from the delegator
|
58
|
+
# handle.
|
59
|
+
#
|
60
|
+
# require 'perobs'
|
61
|
+
#
|
62
|
+
# class Person < PEROBS::Object
|
63
|
+
#
|
64
|
+
# po_attr :name, :mother, :father, :kids
|
65
|
+
#
|
66
|
+
# def initialize(store, name)
|
67
|
+
# super
|
68
|
+
# attr_init(:name, name)
|
69
|
+
# attr_init(:kids, @store.new(PEROBS::Array))
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def to_s
|
73
|
+
# "#{@name} is the child of #{self.mother ? self.mother.name : 'unknown'} " +
|
74
|
+
# "and #{self.father ? self.father.name : 'unknown'}.
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# store = PEROBS::Store.new('family')
|
80
|
+
# store['grandpa'] = joe = store.new(Person, 'Joe')
|
81
|
+
# store['grandma'] = jane = store.new(Person, 'Jane')
|
82
|
+
# jim = store.new(Person, 'Jim')
|
83
|
+
# jim.father = joe
|
84
|
+
# joe.kids << jim
|
85
|
+
# jim.mother = jane
|
86
|
+
# jane.kids << jim
|
87
|
+
# store.sync
|
45
88
|
#
|
46
|
-
# Persistent objects must be created by deriving your class from
|
47
|
-
# PEROBS::Object. Only instance variables that are declared via
|
48
|
-
# po_attr will be persistent. It is recommended that references to other
|
49
|
-
# objects are all going to persistent objects again.
|
50
89
|
class Store
|
51
90
|
|
52
|
-
attr_reader :db, :cache, :class_map
|
91
|
+
attr_reader :db, :cache, :class_map, :object_creation_in_progress
|
53
92
|
|
54
93
|
# Create a new Store.
|
55
94
|
# @param data_base [String] the name of the database
|
@@ -85,6 +124,9 @@ module PEROBS
|
|
85
124
|
# Create a map that can translate classes to numerical IDs and vice
|
86
125
|
# versa.
|
87
126
|
@class_map = ClassMap.new(@db)
|
127
|
+
# This flag is used to check that PEROBS objects are only created via
|
128
|
+
# the Store.new() call by PEROBS users.
|
129
|
+
@object_creation_in_progress = false
|
88
130
|
|
89
131
|
# The Cache reduces read and write latencies by keeping a subset of the
|
90
132
|
# objects in memory.
|
@@ -92,7 +134,8 @@ module PEROBS
|
|
92
134
|
|
93
135
|
# The named (global) objects IDs hashed by their name
|
94
136
|
unless (@root_objects = object_by_id(0))
|
95
|
-
@root_objects = Hash
|
137
|
+
@root_objects = construct_po(Hash)
|
138
|
+
|
96
139
|
# The root object hash always has the object ID 0.
|
97
140
|
@root_objects._change_id(0)
|
98
141
|
# The ID change removes it from the write cache. We need to add it
|
@@ -101,6 +144,26 @@ module PEROBS
|
|
101
144
|
end
|
102
145
|
end
|
103
146
|
|
147
|
+
# You need to call this method to create new PEROBS objects that belong to
|
148
|
+
# this Store.
|
149
|
+
# @param klass [Class] The class of the object you want to create. This
|
150
|
+
# must be a derivative of ObjectBase.
|
151
|
+
# @param *args Optional list of other arguments that are passed to the
|
152
|
+
# constructor of the specified class.
|
153
|
+
# @return [POXReference] A reference to the newly created object.
|
154
|
+
def new(klass, *args)
|
155
|
+
POXReference.new(self, construct_po(klass, *args)._id)
|
156
|
+
end
|
157
|
+
|
158
|
+
# For library internal use only!
|
159
|
+
def construct_po(klass, *args)
|
160
|
+
@object_creation_in_progress = true
|
161
|
+
obj = klass.new(self, *args)
|
162
|
+
@object_creation_in_progress = false
|
163
|
+
obj
|
164
|
+
end
|
165
|
+
|
166
|
+
|
104
167
|
# Delete the entire store. The store is no longer usable after this
|
105
168
|
# method was called.
|
106
169
|
def delete_store
|
@@ -124,6 +187,9 @@ module PEROBS
|
|
124
187
|
return nil
|
125
188
|
end
|
126
189
|
|
190
|
+
if obj.respond_to?(:is_poxreference?)
|
191
|
+
obj = obj._referenced_object
|
192
|
+
end
|
127
193
|
# We only allow derivatives of PEROBS::Object to be stored in the
|
128
194
|
# store.
|
129
195
|
unless obj.is_a?(ObjectBase)
|
data/lib/perobs/version.rb
CHANGED
data/tasks/test.rake
CHANGED
@@ -3,5 +3,6 @@ require 'rspec/core/rake_task'
|
|
3
3
|
|
4
4
|
desc 'Run all RSpec tests in the spec directory'
|
5
5
|
RSpec::Core::RakeTask.new(:test) do |t|
|
6
|
-
t.pattern = 'test/*_spec.rb'
|
6
|
+
t.pattern = Dir.glob('test/*_spec.rb')
|
7
|
+
t.rspec_opts = "-I #{File.join(File.dirname(__FILE__), '..', 'test')}"
|
7
8
|
end
|
data/test/Array_spec.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
|
4
|
+
#
|
5
|
+
# This file contains tests for Array that are similar to the tests for the
|
6
|
+
# Array implementation in MRI. The ideas of these tests were replicated in
|
7
|
+
# this code.
|
8
|
+
#
|
9
|
+
# MIT License
|
10
|
+
#
|
11
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
12
|
+
# a copy of this software and associated documentation files (the
|
13
|
+
# "Software"), to deal in the Software without restriction, including
|
14
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
15
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
16
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
17
|
+
# the following conditions:
|
18
|
+
#
|
19
|
+
# The above copyright notice and this permission notice shall be
|
20
|
+
# included in all copies or substantial portions of the Software.
|
21
|
+
#
|
22
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
23
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
24
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
25
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
26
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
27
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
28
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
|
+
|
30
|
+
require 'spec_helper'
|
31
|
+
|
32
|
+
require 'perobs'
|
33
|
+
|
34
|
+
class PO < PEROBS::Object
|
35
|
+
|
36
|
+
po_attr :name
|
37
|
+
|
38
|
+
def initialize(store, name = nil)
|
39
|
+
super(store)
|
40
|
+
_set(:name, name)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe PEROBS::Array do
|
46
|
+
|
47
|
+
before(:all) do
|
48
|
+
@db_name = generate_db_name(__FILE__)
|
49
|
+
end
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
@store = PEROBS::Store.new(@db_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
after(:each) do
|
56
|
+
@store.delete_store
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should store simple objects persistently' do
|
60
|
+
@store['a'] = a = @store.new(PEROBS::Array)
|
61
|
+
a[0] = 'A'
|
62
|
+
a[1] = 'B'
|
63
|
+
a[2] = po = @store.new(PO)
|
64
|
+
po.name = 'foobar'
|
65
|
+
|
66
|
+
expect(a[0]).to eq('A')
|
67
|
+
expect(a[1]).to eq('B')
|
68
|
+
expect(a[2].name).to eq('foobar')
|
69
|
+
@store.sync
|
70
|
+
|
71
|
+
@store = PEROBS::Store.new(@db_name)
|
72
|
+
a = @store['a']
|
73
|
+
expect(a[0]).to eq('A')
|
74
|
+
expect(a[1]).to eq('B')
|
75
|
+
expect(a[2].name).to eq('foobar')
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should have an each method to iterate' do
|
79
|
+
@store['a'] = a = @store.new(PEROBS::Array)
|
80
|
+
a[0] = 'A'
|
81
|
+
a[1] = 'B'
|
82
|
+
a[2] = 'C'
|
83
|
+
vs = ''
|
84
|
+
a.each { |v| vs << v }
|
85
|
+
expect(vs).to eq('ABC')
|
86
|
+
@store.sync
|
87
|
+
|
88
|
+
@store = PEROBS::Store.new(@db_name)
|
89
|
+
a = @store['a']
|
90
|
+
vs = ''
|
91
|
+
a[3] = @store.new(PO, 'D')
|
92
|
+
a.each { |v| vs << (v.is_a?(String) ? v : v.name) }
|
93
|
+
expect(vs).to eq('ABCD')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Utility method to create a PEROBS::Array from a normal Array.
|
97
|
+
def cpa(ary = nil)
|
98
|
+
a = @store.new(PEROBS::Array)
|
99
|
+
a.replace(ary) unless ary.nil?
|
100
|
+
@store['a'] = a
|
101
|
+
end
|
102
|
+
|
103
|
+
def pcheck
|
104
|
+
yield
|
105
|
+
@store.sync
|
106
|
+
@store = PEROBS::Store.new(@db_name)
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should support reading methods' do
|
111
|
+
expect(cpa([ 1, 1, 3, 5 ]) & cpa([ 1, 2, 3 ])).to eq([ 1, 3 ])
|
112
|
+
expect(cpa & cpa([ 1, 2, 3 ])).to eq([])
|
113
|
+
|
114
|
+
expect(cpa.empty?).to be true
|
115
|
+
expect(cpa([ 0 ]).empty?).to be false
|
116
|
+
|
117
|
+
x = cpa([ 'it', 'came', 'to', 'pass', 'that', '...'])
|
118
|
+
x = x.sort.join(' ')
|
119
|
+
expect(x).to eq('... came it pass that to')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should support Enumberable methods' do
|
123
|
+
x = cpa([ 2, 5, 3, 1, 7 ])
|
124
|
+
expect(x.find { |e| e == 4 }).to be_nil
|
125
|
+
expect(x.find { |e| e == 3 }).to eq(3)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should support re-writing methods' do
|
129
|
+
x = cpa([2, 5, 3, 1, 7])
|
130
|
+
x.sort!{ |a, b| a <=> b }
|
131
|
+
pcheck { expect(x).to eq([ 1, 2, 3, 5, 7 ]) }
|
132
|
+
x.sort!{ |a, b| b - a }
|
133
|
+
pcheck { expect(x).to eq([ 7, 5, 3, 2, 1 ]) }
|
134
|
+
|
135
|
+
x.clear
|
136
|
+
pcheck { expect(x).to eq([]) }
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should support <<()' do
|
140
|
+
a = cpa([ 0, 1, 2 ])
|
141
|
+
a << 4
|
142
|
+
pcheck { expect(a).to eq([ 0, 1, 2, 4 ]) }
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should support []=' do
|
146
|
+
a = cpa([ 0, nil, 2 ])
|
147
|
+
a[1] = 1
|
148
|
+
pcheck { expect(a).to eq([ 0, 1, 2 ]) }
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should support collect!()' do
|
152
|
+
a = cpa([ 1, 'cat', 1..1 ])
|
153
|
+
expect(a.collect! { |e| e.class }).to eq([ Fixnum, String, Range ])
|
154
|
+
pcheck { expect(a).to eq([ Fixnum, String, Range ]) }
|
155
|
+
|
156
|
+
a = cpa([ 1, 'cat', 1..1 ])
|
157
|
+
expect(a.collect! { 99 }).to eq([ 99, 99, 99])
|
158
|
+
pcheck { expect(a).to eq([ 99, 99, 99]) }
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should support map!()' do
|
162
|
+
a = cpa([ 1, 'cat', 1..1 ])
|
163
|
+
expect(a.map! { |e| e.class }).to eq([ Fixnum, String, Range ])
|
164
|
+
pcheck { expect(a).to eq([ Fixnum, String, Range ]) }
|
165
|
+
|
166
|
+
a = cpa([ 1, 'cat', 1..1 ])
|
167
|
+
expect(a.map! { 99 }).to eq([ 99, 99, 99])
|
168
|
+
pcheck { expect(a).to eq ([ 99, 99, 99]) }
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should support fill()' do
|
172
|
+
pcheck { expect(cpa([]).fill(99)).to eq([]) }
|
173
|
+
pcheck { expect(cpa([]).fill(99, 0)).to eq([]) }
|
174
|
+
pcheck { expect(cpa([]).fill(99, 0, 1)).to eq([ 99 ]) }
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should support flatten!()' do
|
178
|
+
a1 = cpa([ 1, 2, 3])
|
179
|
+
a2 = cpa([ 5, 6 ])
|
180
|
+
a3 = cpa([ 4, a2 ])
|
181
|
+
a4 = cpa([ a1, a3 ])
|
182
|
+
pcheck { expect(a4.flatten).to eq([ 1, 2, 3, 4, 5, 6 ]) }
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should support replace()' do
|
186
|
+
a = cpa([ 1, 2, 3])
|
187
|
+
a_id = a.__id__
|
188
|
+
expect(a.replace(cpa([4, 5, 6]))).to eq([ 4, 5, 6 ])
|
189
|
+
pcheck { expect(a).to eq ([ 4, 5, 6 ]) }
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should support insert()' do
|
193
|
+
a = cpa([ 0 ])
|
194
|
+
a.insert(1)
|
195
|
+
pcheck { expect(a).to eq([ 0 ]) }
|
196
|
+
a.insert(1, 1)
|
197
|
+
pcheck { expect(a).to eq([ 0, 1]) }
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'should support push()' do
|
201
|
+
a = cpa([ 1, 2, 3 ])
|
202
|
+
a.push(4, 5)
|
203
|
+
pcheck { expect(a).to eq([ 1, 2, 3, 4, 5 ]) }
|
204
|
+
a.push(nil)
|
205
|
+
pcheck { expect(a).to eq([ 1, 2, 3, 4, 5, nil ]) }
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|