perobs 1.1.0 → 2.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.
- 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
|