perobs 2.1.1 → 2.2.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 +27 -6
- data/lib/perobs/Array.rb +6 -3
- data/lib/perobs/BTreeBlob.rb +9 -3
- data/lib/perobs/BTreeDB.rb +4 -2
- data/lib/perobs/Cache.rb +32 -40
- data/lib/perobs/Handle.rb +44 -0
- data/lib/perobs/Hash.rb +6 -3
- data/lib/perobs/Object.rb +35 -11
- data/lib/perobs/ObjectBase.rb +30 -42
- data/lib/perobs/Store.rb +61 -40
- data/lib/perobs/version.rb +1 -1
- data/test/Array_spec.rb +15 -0
- data/test/Object_spec.rb +11 -3
- data/test/Store_spec.rb +41 -32
- data/test/perobs_spec.rb +3 -3
- metadata +3 -3
- data/lib/perobs/Delegator.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 862c67d14741c0fe0145af7c5eb1c1c147ebe189
|
4
|
+
data.tar.gz: c88f1c19c16db2c6b3f2fd046cbb4d41e9fce2c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11fd013bb0da3088a4ed88dfac390aac5b8cef823fd2bdeb06ab8c7ad4fedbde997365e0f135bae63b72acf5ee9ad3ac10f5640ac6b703785876514be3a480f1
|
7
|
+
data.tar.gz: d13b9dd12084975bc5d6e84e6fcc0f91b3f455e56d4532c7a37983c53dd1054db211e8244c8317da5f0cb85e32ce4ce01564e9a3684b442c01bfdf889735a393
|
data/README.md
CHANGED
@@ -32,8 +32,17 @@ When you derive your own class from PEROBS::Object you need to
|
|
32
32
|
specify which instance variables should be persistent. By using
|
33
33
|
po_attr you can provide a list of symbols that describe the instance
|
34
34
|
variables to persist. This will also create getter and setter methods
|
35
|
-
for these instance varables. You can
|
36
|
-
|
35
|
+
for these instance varables. You can set default values in the
|
36
|
+
constructor . The constructor of PEROBS::ObjectBase derived objects
|
37
|
+
must have at least one argument. The first argument is a PEROBS
|
38
|
+
internal object that must be passed to super() as first thing in
|
39
|
+
initialize(). You can have other arguments if needed. Be aware that
|
40
|
+
initialize() is not called when objects are restored from the
|
41
|
+
database! You can define a restore() method to deal with object
|
42
|
+
initialization or modification after restore from database. restore()
|
43
|
+
is also the proper place to initialize non-persistent instance
|
44
|
+
variables. New objects are created via Store.new() so you cannot call
|
45
|
+
the constructor directly in your code.
|
37
46
|
|
38
47
|
To start off you must create at least one PEROBS::Store object that
|
39
48
|
owns your persistent objects. The store provides the persistent
|
@@ -52,10 +61,10 @@ almost every Ruby data type. YAML is much slower than JSON and Marshal
|
|
52
61
|
is not guaranteed to be compatible between Ruby versions.
|
53
62
|
|
54
63
|
Once you have created a store you can assign objects to it. All
|
55
|
-
persistent objects must be created with
|
64
|
+
persistent objects must be created with Store.new(). This is
|
56
65
|
necessary as you will only deal with proxy objects in your code.
|
57
66
|
Except for the member methods, you will never deal with the objects
|
58
|
-
directly. Instead
|
67
|
+
directly. Instead Store.new() returns a POXReference object that acts
|
59
68
|
as a transparent proxy. This proxy is needed as your code never knows
|
60
69
|
if the actual object is really loaded into the memory or not. PEROBS
|
61
70
|
will handle this transparently for you.
|
@@ -85,13 +94,19 @@ class Person < PEROBS::Object
|
|
85
94
|
|
86
95
|
po_attr :name, :mother, :father, :kids, :spouse, :status
|
87
96
|
|
88
|
-
def initialize(
|
89
|
-
super
|
97
|
+
def initialize(p, name)
|
98
|
+
super(p)
|
90
99
|
attr_init(:name, name)
|
91
100
|
attr_init(:kids, store.new(PEROBS::Array))
|
92
101
|
attr_init(:status, :single)
|
93
102
|
end
|
94
103
|
|
104
|
+
def restore
|
105
|
+
# Use block version of attr_init() to avoid creating unneded
|
106
|
+
# objects. The block is only called when @father doesn't exist yet.
|
107
|
+
attr_init(:father) do { store.new(Person, 'Dad') }
|
108
|
+
end
|
109
|
+
|
95
110
|
def merry(spouse)
|
96
111
|
self.spouse = spouse
|
97
112
|
self.status = :married
|
@@ -148,6 +163,12 @@ when it detects such objects. Just remember to use myself() instead of
|
|
148
163
|
self() if you want to pass a reference to the current persistent
|
149
164
|
object to another object.
|
150
165
|
|
166
|
+
### Caveats and known issues
|
167
|
+
|
168
|
+
PEROBS is currently not thread-safe. You cannot simultaneously access
|
169
|
+
the database from multiple application. You must provide your own
|
170
|
+
locking mechanism to prevent this from happening.
|
171
|
+
|
151
172
|
## Installation
|
152
173
|
|
153
174
|
Add this line to your application's Gemfile:
|
data/lib/perobs/Array.rb
CHANGED
@@ -79,13 +79,16 @@ module PEROBS
|
|
79
79
|
# New PEROBS objects must always be created by calling # Store.new().
|
80
80
|
# PEROBS users should never call this method or equivalents of derived
|
81
81
|
# methods directly.
|
82
|
-
# @param
|
82
|
+
# @param p [PEROBS::Handle] PEROBS handle
|
83
83
|
# @param size [Fixnum] The requested size of the Array
|
84
84
|
# @param default [Any] The default value that is returned when no value is
|
85
85
|
# stored for a specific key.
|
86
|
-
def initialize(
|
87
|
-
super(
|
86
|
+
def initialize(p, size = 0, default = nil)
|
87
|
+
super(p)
|
88
88
|
@data = ::Array.new(size, default)
|
89
|
+
|
90
|
+
# Ensure that the newly created object will be pushed into the database.
|
91
|
+
@store.cache.cache_write(self)
|
89
92
|
end
|
90
93
|
|
91
94
|
# Return a list of all object IDs of all persistend objects that this Array
|
data/lib/perobs/BTreeBlob.rb
CHANGED
@@ -118,7 +118,7 @@ module PEROBS
|
|
118
118
|
|
119
119
|
unless found
|
120
120
|
raise ArgumentError,
|
121
|
-
"Cannot find an entry for ID #{'%016X' % id} to mark"
|
121
|
+
"Cannot find an entry for ID #{'%016X' % id} #{id} to mark"
|
122
122
|
end
|
123
123
|
|
124
124
|
write_index
|
@@ -126,12 +126,15 @@ module PEROBS
|
|
126
126
|
|
127
127
|
# Check if the entry for a given ID is marked.
|
128
128
|
# @param id [Fixnum or Bignum] ID of the entry
|
129
|
+
# @param ignore_errors [Boolean] If set to true no errors will be raised
|
130
|
+
# for non-existing objects.
|
129
131
|
# @return [TrueClass or FalseClass] true if marked, false otherwise
|
130
|
-
def is_marked?(id)
|
132
|
+
def is_marked?(id, ignore_errors = false)
|
131
133
|
@entries.each do |entry|
|
132
134
|
return entry[MARKED] != 0 if entry[ID] == id
|
133
135
|
end
|
134
136
|
|
137
|
+
return false if ignore_errors
|
135
138
|
raise ArgumentError,
|
136
139
|
"Cannot find an entry for ID #{'%016X' % id} to check"
|
137
140
|
end
|
@@ -266,7 +269,10 @@ module PEROBS
|
|
266
269
|
|
267
270
|
# Create a new entry and insert it. The order must match the above
|
268
271
|
# defined constants!
|
269
|
-
|
272
|
+
# Object reads can trigger creation of new objects. As the marking
|
273
|
+
# process triggers reads as well, all newly created objects are always
|
274
|
+
# marked to prevent them from being collected right after creation.
|
275
|
+
entry = [ id, bytes, best_fit_start || end_of_last_entry, 1 ]
|
270
276
|
@entries.insert(best_fit_index, entry)
|
271
277
|
@entries_by_id[id] = entry
|
272
278
|
|
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -173,8 +173,10 @@ module PEROBS
|
|
173
173
|
|
174
174
|
# Check if the object is marked.
|
175
175
|
# @param id [Fixnum or Bignum] ID of the object to check
|
176
|
-
|
177
|
-
|
176
|
+
# @param ignore_errors [Boolean] If set to true no errors will be raised
|
177
|
+
# for non-existing objects.
|
178
|
+
def is_marked?(id, ignore_errors = false)
|
179
|
+
(blob = find_blob(id)) && blob.is_marked?(id, ignore_errors)
|
178
180
|
end
|
179
181
|
|
180
182
|
# Basic consistency check.
|
data/lib/perobs/Cache.rb
CHANGED
@@ -69,7 +69,9 @@ module PEROBS
|
|
69
69
|
# If this condition triggers, we have a bug in the library.
|
70
70
|
raise RuntimeError, "POXReference objects should never be cached"
|
71
71
|
end
|
72
|
+
|
72
73
|
if @transaction_stack.empty?
|
74
|
+
# We are not in transaction mode.
|
73
75
|
idx = index(obj)
|
74
76
|
if (old_obj = @writes[idx]) && old_obj._id != obj._id
|
75
77
|
# There is another old object using this cache slot. Before we can
|
@@ -83,45 +85,29 @@ module PEROBS
|
|
83
85
|
cache_read(obj)
|
84
86
|
# Push the reference of the modified object into the write buffer for
|
85
87
|
# this transaction level.
|
86
|
-
unless @transaction_stack.last.include?(obj)
|
87
|
-
@transaction_stack.last << obj
|
88
|
+
unless @transaction_stack.last.include?(obj._id)
|
89
|
+
@transaction_stack.last << obj._id
|
90
|
+
@transaction_objects[obj._id] = obj
|
88
91
|
end
|
89
92
|
end
|
90
93
|
end
|
91
94
|
|
92
|
-
# Remove an object from the write cache. This will prevent a modified
|
93
|
-
# object from being written to the back-end store.
|
94
|
-
def unwrite(obj)
|
95
|
-
if @transaction_stack.empty?
|
96
|
-
idx = index(obj)
|
97
|
-
if (old_obj = @writes[idx]).nil? || old_obj._id != obj._id
|
98
|
-
raise RuntimeError, "Object to unwrite is not in cache"
|
99
|
-
end
|
100
|
-
@writes[idx] = nil
|
101
|
-
else
|
102
|
-
unless @transaction_stack.last.include?(obj)
|
103
|
-
raise RuntimeError, 'unwrite failed'
|
104
|
-
end
|
105
|
-
@transaction_stack.last.delete(obj)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
95
|
# Return the PEROBS::Object with the specified ID or nil if not found.
|
110
96
|
# @param id [Fixnum or Bignum] ID of the cached PEROBS::ObjectBase
|
111
|
-
def object_by_id(id)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
97
|
+
#def object_by_id(id)
|
98
|
+
# idx = id & @mask
|
99
|
+
# # The index is just a hash. We still need to check if the object IDs are
|
100
|
+
# # actually the same before we can return the object.
|
101
|
+
# if (obj = @writes[idx]) && obj._id == id
|
102
|
+
# # The object was in the write cache.
|
103
|
+
# return obj
|
104
|
+
# elsif (obj = @reads[idx]) && obj._id == id
|
105
|
+
# # The object was in the read cache.
|
106
|
+
# return obj
|
107
|
+
# end
|
108
|
+
|
109
|
+
# nil
|
110
|
+
#end
|
125
111
|
|
126
112
|
# Flush all pending writes to the persistant storage back-end.
|
127
113
|
def flush
|
@@ -140,12 +126,14 @@ module PEROBS
|
|
140
126
|
# active, the write cached is flushed before the transaction is started.
|
141
127
|
def begin_transaction
|
142
128
|
if @transaction_stack.empty?
|
143
|
-
#
|
144
|
-
# the current state of all objects.
|
129
|
+
# The new transaction is the top-level transaction. Flush the write
|
130
|
+
# buffer to save the current state of all objects.
|
145
131
|
flush
|
146
132
|
else
|
147
|
-
|
148
|
-
|
133
|
+
# Save a copy of all objects that were modified during the enclosing
|
134
|
+
# transaction.
|
135
|
+
@transaction_stack.last.each do |id|
|
136
|
+
@transaction_objects[id]._stash(@transaction_stack.length - 1)
|
149
137
|
end
|
150
138
|
end
|
151
139
|
# Push a transaction buffer onto the transaction stack. This buffer will
|
@@ -163,7 +151,8 @@ module PEROBS
|
|
163
151
|
when 1
|
164
152
|
# All transactions completed successfully. Write all modified objects
|
165
153
|
# into the backend storage.
|
166
|
-
@transaction_stack.pop.each { |
|
154
|
+
@transaction_stack.pop.each { |id| @transaction_objects[id]._sync }
|
155
|
+
@transaction_objects = ::Hash.new
|
167
156
|
else
|
168
157
|
# A nested transaction completed successfully. We add the list of
|
169
158
|
# modified objects to the list of the enclosing transaction.
|
@@ -182,7 +171,9 @@ module PEROBS
|
|
182
171
|
if @transaction_stack.empty?
|
183
172
|
raise RuntimeError, 'No ongoing transaction to abort'
|
184
173
|
end
|
185
|
-
@transaction_stack.pop.each
|
174
|
+
@transaction_stack.pop.each do |id|
|
175
|
+
@transaction_objects[id]._restore(@transaction_stack.length)
|
176
|
+
end
|
186
177
|
end
|
187
178
|
|
188
179
|
# Clear all cached entries. You must call flush before calling this
|
@@ -193,7 +184,8 @@ module PEROBS
|
|
193
184
|
# the read or write cache Arrays.
|
194
185
|
@reads = ::Array.new(2 ** @bits)
|
195
186
|
@writes = ::Array.new(2 ** @bits)
|
196
|
-
@transaction_stack =
|
187
|
+
@transaction_stack = ::Array.new
|
188
|
+
@transaction_objects = ::Hash.new
|
197
189
|
end
|
198
190
|
|
199
191
|
# Don't include the cache buffers in output of other objects that
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = Handle.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
module PEROBS
|
29
|
+
|
30
|
+
# The Handle is the first paramter of the initialize() method of every
|
31
|
+
# PEROBS object. By convention this parameter should be called 'p'.
|
32
|
+
class Handle
|
33
|
+
|
34
|
+
attr_reader :store, :id
|
35
|
+
|
36
|
+
def initialize(store, id)
|
37
|
+
@store = store
|
38
|
+
@id = id
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
data/lib/perobs/Hash.rb
CHANGED
@@ -77,13 +77,16 @@ module PEROBS
|
|
77
77
|
# New PEROBS objects must always be created by calling # Store.new().
|
78
78
|
# PEROBS users should never call this method or equivalents of derived
|
79
79
|
# methods directly.
|
80
|
-
# @param
|
80
|
+
# @param p [PEROBS::Handle] PEROBS handle
|
81
81
|
# @param default [Any] The default value that is returned when no value is
|
82
82
|
# stored for a specific key.
|
83
|
-
def initialize(
|
84
|
-
super(
|
83
|
+
def initialize(p, default = nil)
|
84
|
+
super(p)
|
85
85
|
@default = nil
|
86
86
|
@data = {}
|
87
|
+
|
88
|
+
# Ensure that the newly created object will be pushed into the database.
|
89
|
+
@store.cache.cache_write(self)
|
87
90
|
end
|
88
91
|
|
89
92
|
# Return a list of all object IDs of all persistend objects that this Hash
|
data/lib/perobs/Object.rb
CHANGED
@@ -79,14 +79,17 @@ module PEROBS
|
|
79
79
|
# New PEROBS objects must always be created by calling # Store.new().
|
80
80
|
# PEROBS users should never call this method or equivalents of derived
|
81
81
|
# methods directly.
|
82
|
-
|
83
|
-
|
82
|
+
# @param p [PEROBS::Handle] PEROBS handle
|
83
|
+
def initialize(p)
|
84
|
+
super(p)
|
85
|
+
|
86
|
+
# Ensure that the newly created object will be pushed into the database.
|
87
|
+
@store.cache.cache_write(self)
|
84
88
|
end
|
85
89
|
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
# object was reconstructed from the store.
|
90
|
+
# This method is deprecated. It will be removed in future versions. Please
|
91
|
+
# use attr_init() instead.
|
92
|
+
# the database.
|
90
93
|
# @param attr [Symbol] Name of the attribute
|
91
94
|
# @param val [Any] Value to be set
|
92
95
|
# @return [true|false] True if the value was initialized, otherwise false.
|
@@ -99,6 +102,26 @@ module PEROBS
|
|
99
102
|
false
|
100
103
|
end
|
101
104
|
|
105
|
+
# Use this method to initialize persistent attributes in the restore()
|
106
|
+
# method that have not yet been initialized. This is the case when the
|
107
|
+
# object was saved with an earlier version of the program that did not yet
|
108
|
+
# have the instance variable. If you want to assign another PEROBS object
|
109
|
+
# to the variable you should use the block variant to avoid unnecessary
|
110
|
+
# creation of PEROBS object that later need to be collected again.
|
111
|
+
def attr_init(attr, val = nil, &block)
|
112
|
+
if _all_attributes.include?(attr)
|
113
|
+
unless instance_variable_defined?('@' + attr.to_s)
|
114
|
+
_set(attr, block_given? ? yield : val)
|
115
|
+
end
|
116
|
+
return true
|
117
|
+
else
|
118
|
+
raise ArgumentError, "'#{attr}' is not a defined persistent " +
|
119
|
+
"attribute of class #{self.class}"
|
120
|
+
end
|
121
|
+
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
102
125
|
# Call this method to manually mark the object as modified. This is
|
103
126
|
# necessary if you are using the '@' notation to access instance variables
|
104
127
|
# during assignment operations (=, +=, -=, etc.). To avoid having to call
|
@@ -154,13 +177,13 @@ module PEROBS
|
|
154
177
|
# Textual dump for debugging purposes
|
155
178
|
# @return [String]
|
156
179
|
def inspect
|
157
|
-
"{\n" +
|
180
|
+
"#{to_s}:#{@_id}\n{\n" +
|
158
181
|
_all_attributes.map do |attr|
|
159
182
|
ivar = ('@' + attr.to_s).to_sym
|
160
|
-
if (value = instance_variable_get(ivar)).respond_to?(
|
161
|
-
" #{attr}
|
183
|
+
if (value = instance_variable_get(ivar)).respond_to?(:is_poxreference?)
|
184
|
+
" #{attr} => <PEROBS::ObjectBase:#{value._id}>"
|
162
185
|
else
|
163
|
-
" #{attr}
|
186
|
+
" #{attr} => #{value.inspect}"
|
164
187
|
end
|
165
188
|
end.join(",\n") +
|
166
189
|
"\n}\n"
|
@@ -195,7 +218,8 @@ module PEROBS
|
|
195
218
|
'you are trying to assign here?'
|
196
219
|
end
|
197
220
|
instance_variable_set(('@' + attr.to_s).to_sym, val)
|
198
|
-
# Let the store know that we have a modified object.
|
221
|
+
# Let the store know that we have a modified object. If we restored the
|
222
|
+
# object from the DB, we don't mark it as modified.
|
199
223
|
mark_as_modified
|
200
224
|
|
201
225
|
val
|
data/lib/perobs/ObjectBase.rb
CHANGED
@@ -120,23 +120,25 @@ module PEROBS
|
|
120
120
|
# New PEROBS objects must always be created by calling # Store.new().
|
121
121
|
# PEROBS users should never call this method or equivalents of derived
|
122
122
|
# methods directly.
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
123
|
+
# @param p [PEROBS::Handle] PEROBS handle
|
124
|
+
def initialize(p)
|
125
|
+
_initialize(p)
|
126
|
+
end
|
127
|
+
|
128
|
+
# This is the real code for initialize. It is called from initialize() but
|
129
|
+
# also when we restore objects from the database. In the later case, we
|
130
|
+
# don't call the regular constructors. But this code must be exercised on
|
131
|
+
# object creation with new() and on restore from DB.
|
132
|
+
# param p [PEROBS::Handle] PEROBS handle
|
133
|
+
def _initialize(p)
|
134
|
+
@store = p.store
|
135
|
+
@_id = p.id
|
131
136
|
@store._register_in_memory(self, @_id)
|
132
137
|
ObjectSpace.define_finalizer(self, ObjectBase._finalize(@store, @_id))
|
133
138
|
@_stash_map = nil
|
134
139
|
# Allocate a proxy object for this object. User code should only operate
|
135
140
|
# on this proxy, never on self.
|
136
141
|
@myself = POXReference.new(@store, @_id)
|
137
|
-
|
138
|
-
# Let the store know that we have a modified object.
|
139
|
-
@store.cache.cache_write(self)
|
140
142
|
end
|
141
143
|
|
142
144
|
# This method generates the destructor for the objects of this class. It
|
@@ -150,8 +152,10 @@ module PEROBS
|
|
150
152
|
# This method can be overloaded by derived classes to do some massaging on
|
151
153
|
# the data after it has been restored from the database. This could either
|
152
154
|
# be some sanity check or code to migrate the object from one version to
|
153
|
-
# another.
|
154
|
-
|
155
|
+
# another. It is also the right place to initialize non-persistent
|
156
|
+
# instance variables as initialize() will only be called when objects are
|
157
|
+
# created for the first time.
|
158
|
+
def restore
|
155
159
|
end
|
156
160
|
|
157
161
|
# Two objects are considered equal if their object IDs are the same.
|
@@ -181,9 +185,10 @@ module PEROBS
|
|
181
185
|
|
182
186
|
klass = store.class_map.id_to_class(db_obj['class_id'])
|
183
187
|
# Call the constructor of the specified class.
|
184
|
-
obj =
|
188
|
+
obj = Object.const_get(klass).allocate
|
189
|
+
obj._initialize(Handle.new(store, id))
|
185
190
|
obj._deserialize(db_obj['data'])
|
186
|
-
obj.
|
191
|
+
obj.restore
|
187
192
|
|
188
193
|
obj
|
189
194
|
end
|
@@ -195,22 +200,21 @@ module PEROBS
|
|
195
200
|
# any previous stash level or in the regular object DB. If the object
|
196
201
|
# was created during the transaction, there is not previous state to
|
197
202
|
# restore to.
|
198
|
-
|
203
|
+
data = nil
|
199
204
|
if @_stash_map
|
200
205
|
(level - 1).downto(0) do |lvl|
|
201
206
|
if @_stash_map[lvl]
|
202
|
-
|
207
|
+
data = @_stash_map[lvl]
|
203
208
|
break
|
204
209
|
end
|
205
210
|
end
|
206
211
|
end
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
db_obj = store.db.get_object(id)
|
212
|
+
if data
|
213
|
+
# We have a stashed version that we can restore from.
|
214
|
+
_deserialize(data)
|
215
|
+
elsif @store.db.include?(@_id)
|
216
|
+
# We have no stashed version but can restore from the database.
|
217
|
+
db_obj = store.db.get_object(@_id)
|
214
218
|
_deserialize(db_obj['data'])
|
215
219
|
end
|
216
220
|
end
|
@@ -219,25 +223,9 @@ module PEROBS
|
|
219
223
|
# back-end. The object gets a new ID that is stored in @_stash_map to map
|
220
224
|
# the stash ID back to the original data.
|
221
225
|
def _stash(level)
|
222
|
-
|
223
|
-
'class' => self.class.to_s,
|
224
|
-
'data' => _serialize
|
225
|
-
}
|
226
|
-
@_stash_map = [] unless @_stash_map
|
226
|
+
@_stash_map ||= ::Array.new
|
227
227
|
# Get a new ID to store this version of the object.
|
228
|
-
@_stash_map[level] =
|
229
|
-
@store.db.put_object(db_obj, stash_id)
|
230
|
-
end
|
231
|
-
|
232
|
-
# Library internal method. Do not use outside of this library.
|
233
|
-
# @private
|
234
|
-
def _change_id(id)
|
235
|
-
# Unregister the object with the old ID from the write cache to prevent
|
236
|
-
# cache corruption. The objects are index by ID in the cache.
|
237
|
-
@store.cache.unwrite(self)
|
238
|
-
@store._collect(@_id)
|
239
|
-
@store._register_in_memory(self, id)
|
240
|
-
@_id = id
|
228
|
+
@_stash_map[level] = _serialize
|
241
229
|
end
|
242
230
|
|
243
231
|
end
|
data/lib/perobs/Store.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
require 'set'
|
29
29
|
require 'weakref'
|
30
30
|
|
31
|
+
require 'perobs/Handle'
|
31
32
|
require 'perobs/Cache'
|
32
33
|
require 'perobs/ClassMap'
|
33
34
|
require 'perobs/BTreeDB'
|
@@ -38,6 +39,9 @@ require 'perobs/Array'
|
|
38
39
|
# PErsistent Ruby OBject Store
|
39
40
|
module PEROBS
|
40
41
|
|
42
|
+
Statistics = Struct.new(:in_memory_objects, :root_objects,
|
43
|
+
:marked_objects, :swept_objects)
|
44
|
+
|
41
45
|
# PEROBS::Store is a persistent storage system for Ruby objects. Regular
|
42
46
|
# Ruby objects are transparently stored in a back-end storage and retrieved
|
43
47
|
# when needed. It features a garbage collector that removes all objects that
|
@@ -64,8 +68,8 @@ module PEROBS
|
|
64
68
|
#
|
65
69
|
# po_attr :name, :mother, :father, :kids
|
66
70
|
#
|
67
|
-
# def initialize(
|
68
|
-
# super
|
71
|
+
# def initialize(cf, name)
|
72
|
+
# super(cf)
|
69
73
|
# attr_init(:name, name)
|
70
74
|
# attr_init(:kids, @store.new(PEROBS::Array))
|
71
75
|
# end
|
@@ -89,7 +93,7 @@ module PEROBS
|
|
89
93
|
#
|
90
94
|
class Store
|
91
95
|
|
92
|
-
attr_reader :db, :cache, :class_map
|
96
|
+
attr_reader :db, :cache, :class_map
|
93
97
|
|
94
98
|
# Create a new Store.
|
95
99
|
# @param data_base [String] the name of the database
|
@@ -125,14 +129,14 @@ module PEROBS
|
|
125
129
|
# Create a map that can translate classes to numerical IDs and vice
|
126
130
|
# versa.
|
127
131
|
@class_map = ClassMap.new(@db)
|
128
|
-
# This flag is used to check that PEROBS objects are only created via
|
129
|
-
# the Store.new() call by PEROBS users.
|
130
|
-
@object_creation_in_progress = false
|
131
132
|
|
132
133
|
# List of PEROBS objects that are currently available as Ruby objects
|
133
134
|
# hashed by their ID.
|
134
135
|
@in_memory_objects = {}
|
135
136
|
|
137
|
+
# This objects keeps some counters of interest.
|
138
|
+
@stats = Statistics.new
|
139
|
+
|
136
140
|
# The Cache reduces read and write latencies by keeping a subset of the
|
137
141
|
# objects in memory.
|
138
142
|
@cache = Cache.new(options[:cache_bits] || 16)
|
@@ -141,8 +145,7 @@ module PEROBS
|
|
141
145
|
unless (@root_objects = object_by_id(0))
|
142
146
|
# The root object hash always has the object ID 0.
|
143
147
|
@root_objects = _construct_po(Hash, 0)
|
144
|
-
#
|
145
|
-
# again.
|
148
|
+
# Mark the root_objects object as modified.
|
146
149
|
@cache.cache_write(@root_objects)
|
147
150
|
end
|
148
151
|
end
|
@@ -151,29 +154,29 @@ module PEROBS
|
|
151
154
|
# this Store.
|
152
155
|
# @param klass [Class] The class of the object you want to create. This
|
153
156
|
# must be a derivative of ObjectBase.
|
154
|
-
# @param
|
157
|
+
# @param args Optional list of other arguments that are passed to the
|
155
158
|
# constructor of the specified class.
|
156
159
|
# @return [POXReference] A reference to the newly created object.
|
157
160
|
def new(klass, *args)
|
158
|
-
|
161
|
+
unless klass.is_a?(BasicObject)
|
162
|
+
raise ArgumentError, "#{klass} is not a BasicObject derivative"
|
163
|
+
end
|
164
|
+
|
165
|
+
obj = _construct_po(klass, _new_id, *args)
|
166
|
+
# Mark the new object as modified so it gets pushed into the database.
|
167
|
+
@cache.cache_write(obj)
|
168
|
+
# Return a POXReference proxy for the newly created object.
|
169
|
+
obj.myself
|
159
170
|
end
|
160
171
|
|
161
172
|
# For library internal use only!
|
162
173
|
# This method will create a new PEROBS object.
|
163
174
|
# @param klass [BasicObject] Class of the object to create
|
164
|
-
# @param id [Fixnum, Bignum
|
165
|
-
# @param
|
175
|
+
# @param id [Fixnum, Bignum] Requested object ID
|
176
|
+
# @param args [Array] Arguments to pass to the object constructor.
|
166
177
|
# @return [BasicObject] Newly constructed PEROBS object
|
167
178
|
def _construct_po(klass, id, *args)
|
168
|
-
|
169
|
-
raise ArgumentError, "#{klass} is not a BasicObject derivative"
|
170
|
-
end
|
171
|
-
@object_creation_in_progress = true
|
172
|
-
obj = klass.new(self, *args)
|
173
|
-
@object_creation_in_progress = false
|
174
|
-
# If a specific object ID was requested we need to set it now.
|
175
|
-
obj._change_id(id) if id
|
176
|
-
obj
|
179
|
+
klass.new(Handle.new(self, id), *args)
|
177
180
|
end
|
178
181
|
|
179
182
|
# Delete the entire store. The store is no longer usable after this
|
@@ -230,7 +233,7 @@ module PEROBS
|
|
230
233
|
# needed.
|
231
234
|
def sync
|
232
235
|
if @cache.in_transaction?
|
233
|
-
raise RuntimeError, 'You cannot call sync during a transaction'
|
236
|
+
raise RuntimeError, 'You cannot call sync() during a transaction'
|
234
237
|
end
|
235
238
|
@cache.flush
|
236
239
|
end
|
@@ -241,6 +244,9 @@ module PEROBS
|
|
241
244
|
# method periodically.
|
242
245
|
# @return [Fixnum] The number of collected objects
|
243
246
|
def gc
|
247
|
+
if @cache.in_transaction?
|
248
|
+
raise RuntimeError, 'You cannot call gc() during a transaction'
|
249
|
+
end
|
244
250
|
sync
|
245
251
|
mark
|
246
252
|
sweep
|
@@ -282,7 +288,7 @@ module PEROBS
|
|
282
288
|
# @param repair [TrueClass/FalseClass] true if a repair attempt should be
|
283
289
|
# made.
|
284
290
|
# @return [Fixnum] The number of references to bad objects found.
|
285
|
-
def check(repair =
|
291
|
+
def check(repair = false)
|
286
292
|
# All objects must have in-db version.
|
287
293
|
sync
|
288
294
|
# Run basic consistency checks first.
|
@@ -325,16 +331,18 @@ module PEROBS
|
|
325
331
|
# Start with the object 0 and the indexes of the root objects. Push them
|
326
332
|
# onto the work stack.
|
327
333
|
stack = [ 0 ] + @root_objects.values
|
328
|
-
stack.each { |id| @db.mark(id) }
|
329
334
|
while !stack.empty?
|
330
335
|
# Get an object index from the stack.
|
331
|
-
obj = object_by_id(id = stack.pop)
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
336
|
+
unless (obj = object_by_id(id = stack.pop))
|
337
|
+
raise RuntimeError, "Database is corrupted. Object with ID #{id} " +
|
338
|
+
"not found."
|
339
|
+
end
|
340
|
+
# Mark the object so it will never be pushed to the stack again.
|
341
|
+
@db.mark(id)
|
342
|
+
yield(obj.myself) if block_given?
|
343
|
+
# Push the IDs of all unmarked referenced objects onto the stack
|
344
|
+
obj._referenced_object_ids.each do |r_id|
|
345
|
+
stack << r_id unless @db.is_marked?(r_id)
|
338
346
|
end
|
339
347
|
end
|
340
348
|
end
|
@@ -382,10 +390,10 @@ module PEROBS
|
|
382
390
|
|
383
391
|
# This method returns a Hash with some statistics about this store.
|
384
392
|
def statistics
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
393
|
+
@stats.in_memory_objects = @in_memory_objects.length
|
394
|
+
@stats.root_objects = @root_objects.length
|
395
|
+
|
396
|
+
@stats
|
389
397
|
end
|
390
398
|
|
391
399
|
private
|
@@ -394,16 +402,21 @@ module PEROBS
|
|
394
402
|
# objects that are reachable from the root objects.
|
395
403
|
def mark
|
396
404
|
classes = Set.new
|
397
|
-
|
405
|
+
marked_objects = 0
|
406
|
+
each { |obj| classes.add(obj.class); marked_objects += 1 }
|
398
407
|
@class_map.keep(classes.map { |c| c.to_s })
|
408
|
+
|
409
|
+
# The root_objects object is included in the count, but we only want to
|
410
|
+
# count user objects here.
|
411
|
+
@stats.marked_objects = marked_objects - 1
|
399
412
|
end
|
400
413
|
|
401
414
|
# Sweep phase of a mark-and-sweep garbage collector. It will remove all
|
402
415
|
# unmarked objects from the store.
|
403
416
|
def sweep
|
404
|
-
|
417
|
+
@stats.swept_objects = @db.delete_unmarked_objects.length
|
405
418
|
@cache.reset
|
406
|
-
|
419
|
+
@stats.swept_objects
|
407
420
|
end
|
408
421
|
|
409
422
|
# Check the object with the given start_id and all other objects that are
|
@@ -431,11 +444,19 @@ module PEROBS
|
|
431
444
|
obj._referenced_object_ids.each do |refd_id|
|
432
445
|
# Push them onto the todo list unless they have been marked
|
433
446
|
# already.
|
434
|
-
todo_list << [ obj, refd_id ] unless @db.is_marked?(refd_id)
|
447
|
+
todo_list << [ obj, refd_id ] unless @db.is_marked?(refd_id, true)
|
435
448
|
end
|
436
449
|
else
|
437
450
|
# Remove references to bad objects.
|
438
|
-
|
451
|
+
if ref_obj && repair
|
452
|
+
$stderr.puts "Fixing broken reference to #{id} in\n" +
|
453
|
+
ref_obj.inspect
|
454
|
+
ref_obj._delete_reference_to_id(id)
|
455
|
+
else
|
456
|
+
raise RuntimeError,
|
457
|
+
"The following object references a non-existing object #{id}:\n" +
|
458
|
+
ref_obj.inspect
|
459
|
+
end
|
439
460
|
errors += 1
|
440
461
|
end
|
441
462
|
end
|
data/lib/perobs/version.rb
CHANGED
data/test/Array_spec.rb
CHANGED
@@ -60,6 +60,21 @@ describe PEROBS::Array do
|
|
60
60
|
@store.delete_store
|
61
61
|
end
|
62
62
|
|
63
|
+
it 'should store an empty Array persistently' do
|
64
|
+
@store['a'] = @store.new(PEROBS::Array)
|
65
|
+
@store.transaction do
|
66
|
+
@store['b'] = @store.new(PEROBS::Array)
|
67
|
+
end
|
68
|
+
@store.sync
|
69
|
+
@store = nil
|
70
|
+
GC.start
|
71
|
+
|
72
|
+
@store = PEROBS::Store.new(@db_name)
|
73
|
+
a = @store['a']
|
74
|
+
expect(a.length).to eq(0)
|
75
|
+
expect(@store['b'].length).to eq(0)
|
76
|
+
end
|
77
|
+
|
63
78
|
it 'should store simple objects persistently' do
|
64
79
|
@store['a'] = a = @store.new(PEROBS::Array)
|
65
80
|
a[0] = 'A'
|
data/test/Object_spec.rb
CHANGED
@@ -42,9 +42,9 @@ class O2 < PEROBS::Object
|
|
42
42
|
|
43
43
|
def initialize(store)
|
44
44
|
super
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
attr_init(:a1, 'a1')
|
46
|
+
attr_init(:a2, nil)
|
47
|
+
attr_init(:a4, 42)
|
48
48
|
end
|
49
49
|
|
50
50
|
def a3_deref
|
@@ -110,15 +110,23 @@ describe PEROBS::Store do
|
|
110
110
|
o2.a3 = o1
|
111
111
|
o2.a4 = @store.new(PEROBS::Array)
|
112
112
|
o2.a4 += [ 0, 1, 2 ]
|
113
|
+
@store.transaction do
|
114
|
+
@store['o3'] = o3 = @store.new(O1)
|
115
|
+
o3.a1 = @store.new(PEROBS::Array)
|
116
|
+
end
|
113
117
|
@store.sync
|
118
|
+
@store = nil
|
119
|
+
GC.start
|
114
120
|
|
115
121
|
@store = PEROBS::Store.new(@db_name)
|
116
122
|
o1 = @store['o1']
|
117
123
|
o2 = @store['o2']
|
124
|
+
o3 = @store['o3']
|
118
125
|
expect(o1.a1).to eq('a1')
|
119
126
|
expect(o2.a1).to be_nil
|
120
127
|
expect(o2.a3).to eq(o1)
|
121
128
|
expect(o2.a4).to eq([ 0, 1, 2 ])
|
129
|
+
expect(o3.a1).to eq([])
|
122
130
|
end
|
123
131
|
|
124
132
|
it 'should transparently access a referenced object' do
|
data/test/Store_spec.rb
CHANGED
@@ -35,9 +35,9 @@ class Person < PEROBS::Object
|
|
35
35
|
|
36
36
|
def initialize(store)
|
37
37
|
super
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
attr_init(:name, '')
|
39
|
+
attr_init(:bmi, 22.2)
|
40
|
+
attr_init(:married, false)
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -48,30 +48,30 @@ class PersonN < PEROBS::Object
|
|
48
48
|
|
49
49
|
def initialize(store)
|
50
50
|
super
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
attr_init(:name, '')
|
52
|
+
attr_init(:bmi, 22.2)
|
53
|
+
attr_init(:married, false)
|
54
54
|
end
|
55
55
|
|
56
56
|
end
|
57
57
|
|
58
58
|
class O0 < PEROBS::Object
|
59
59
|
|
60
|
-
po_attr :
|
60
|
+
po_attr :child
|
61
61
|
|
62
62
|
def initialize(store)
|
63
63
|
super
|
64
|
-
|
64
|
+
self.child = @store.new(O1, myself)
|
65
65
|
end
|
66
66
|
|
67
67
|
end
|
68
68
|
class O1 < PEROBS::Object
|
69
69
|
|
70
|
-
po_attr :
|
70
|
+
po_attr :parent
|
71
71
|
|
72
72
|
def initialize(store, p = nil)
|
73
73
|
super(store)
|
74
|
-
parent = p
|
74
|
+
self.parent = p
|
75
75
|
end
|
76
76
|
|
77
77
|
end
|
@@ -145,11 +145,6 @@ describe PEROBS::Store do
|
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
148
|
-
it 'should not allow calls to BasicObject.new()' do
|
149
|
-
@store = PEROBS::Store.new(@db_file)
|
150
|
-
expect { Person.new(@store) }.to raise_error RuntimeError
|
151
|
-
end
|
152
|
-
|
153
148
|
it 'should flush cached objects when necessary' do
|
154
149
|
@store = PEROBS::Store.new(@db_file, :cache_bits => 3)
|
155
150
|
last_obj = nil
|
@@ -431,11 +426,13 @@ describe PEROBS::Store do
|
|
431
426
|
|
432
427
|
it 'should handle nested constructors' do
|
433
428
|
@store = PEROBS::Store.new(@db_file)
|
434
|
-
@store['
|
429
|
+
@store['root'] = @store.new(O0)
|
435
430
|
@store.sync
|
436
431
|
expect(@store.check).to eq(0)
|
432
|
+
|
437
433
|
@store = PEROBS::Store.new(@db_file)
|
438
434
|
expect(@store.check).to eq(0)
|
435
|
+
expect(@store['root'].child.parent).to eq(@store['root'])
|
439
436
|
end
|
440
437
|
|
441
438
|
it 'should survive a real world usage test' do
|
@@ -443,58 +440,70 @@ describe PEROBS::Store do
|
|
443
440
|
@store = PEROBS::Store.new(@db_file, options)
|
444
441
|
ref = {}
|
445
442
|
|
446
|
-
|
443
|
+
deletions_since_last_gc = 0
|
444
|
+
0.upto(5000) do |i|
|
447
445
|
key = "o#{i}"
|
448
|
-
case
|
446
|
+
case rand(8)
|
449
447
|
when 0
|
448
|
+
# Add 'A' person
|
450
449
|
value = 'A' * rand(512)
|
451
450
|
@store[key] = p = @store.new(Person)
|
452
451
|
p.name = value
|
453
452
|
ref[key] = value
|
454
|
-
@store.sync
|
455
453
|
when 1
|
454
|
+
# Add 'B' person
|
456
455
|
value = 'B' * rand(128)
|
457
456
|
@store[key] = p = @store.new(Person)
|
458
457
|
p.name = value
|
459
458
|
ref[key] = value
|
460
459
|
when 2
|
461
|
-
|
462
|
-
if
|
463
|
-
key =
|
460
|
+
# Delete a root entry
|
461
|
+
if ref.keys.length > 11
|
462
|
+
key = ref.keys[(ref.keys.length / 11).to_i]
|
463
|
+
expect(@store[key]).not_to be_nil
|
464
464
|
@store[key] = nil
|
465
465
|
ref.delete(key)
|
466
|
+
deletions_since_last_gc += 1
|
466
467
|
end
|
467
468
|
when 3
|
468
|
-
|
469
|
+
# Call garbage collector
|
470
|
+
if rand(30) == 0
|
471
|
+
@store.gc
|
472
|
+
stats = @store.statistics
|
473
|
+
expect(stats.marked_objects).to eq(ref.length)
|
474
|
+
expect(stats.swept_objects).to eq(deletions_since_last_gc)
|
475
|
+
deletions_since_last_gc = 0
|
476
|
+
expect(@store.gc).to eq(deletions_since_last_gc)
|
477
|
+
end
|
469
478
|
when 4
|
479
|
+
# Sync store and reload
|
470
480
|
if rand(15) == 0
|
471
481
|
@store.sync
|
472
482
|
@store = PEROBS::Store.new(@db_file, options)
|
473
483
|
end
|
474
484
|
when 5
|
475
|
-
|
476
|
-
if
|
477
|
-
key =
|
485
|
+
# Replace an entry with 'C' person
|
486
|
+
if ref.keys.length > 13
|
487
|
+
key = ref.keys[(ref.keys.length / 13).to_i]
|
478
488
|
value = 'C' * rand(1024)
|
479
489
|
@store[key] = p = @store.new(Person)
|
480
490
|
p.name = value
|
481
491
|
ref[key] = value
|
492
|
+
deletions_since_last_gc += 1
|
482
493
|
end
|
483
494
|
when 6
|
495
|
+
# Sync and check store
|
484
496
|
if rand(50) == 0
|
485
497
|
@store.sync
|
486
498
|
expect(@store.check(false)).to eq(0)
|
487
499
|
end
|
488
500
|
when 7
|
489
|
-
|
490
|
-
if ref
|
501
|
+
# Compare a random entry with reference entry
|
502
|
+
if ref.keys.length > 0
|
503
|
+
key = ref.keys[rand(ref.keys.length - 1)]
|
491
504
|
expect(@store[key].name).to eq(ref[key])
|
492
505
|
end
|
493
506
|
end
|
494
|
-
|
495
|
-
if ref[key]
|
496
|
-
expect(@store[key].name).to eq(ref[key])
|
497
|
-
end
|
498
507
|
end
|
499
508
|
|
500
509
|
ref.each do |k, v|
|
data/test/perobs_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -71,8 +71,8 @@ files:
|
|
71
71
|
- lib/perobs/Cache.rb
|
72
72
|
- lib/perobs/ClassMap.rb
|
73
73
|
- lib/perobs/DataBase.rb
|
74
|
-
- lib/perobs/Delegator.rb
|
75
74
|
- lib/perobs/DynamoDB.rb
|
75
|
+
- lib/perobs/Handle.rb
|
76
76
|
- lib/perobs/Hash.rb
|
77
77
|
- lib/perobs/Object.rb
|
78
78
|
- lib/perobs/ObjectBase.rb
|
data/lib/perobs/Delegator.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
#
|
3
|
-
# = Delegator.rb -- Persistent Ruby Object Store
|
4
|
-
#
|
5
|
-
# Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
-
#
|
7
|
-
# MIT License
|
8
|
-
#
|
9
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
-
# a copy of this software and associated documentation files (the
|
11
|
-
# "Software"), to deal in the Software without restriction, including
|
12
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
-
# the following conditions:
|
16
|
-
#
|
17
|
-
# The above copyright notice and this permission notice shall be
|
18
|
-
# included in all copies or substantial portions of the Software.
|
19
|
-
#
|
20
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
-
|
28
|
-
require 'perobs/ClassMap'
|
29
|
-
|
30
|
-
module PEROBS
|
31
|
-
|
32
|
-
# This Delegator module provides the methods to turn the PEROBS::Array and
|
33
|
-
# PEROBS::Hash into proxies for Array and Hash.
|
34
|
-
module Delegator
|
35
|
-
|
36
|
-
# Proxy all calls to unknown methods to the data object.
|
37
|
-
def method_missing(method_sym, *args, &block)
|
38
|
-
if self.class::READERS.include?(method_sym) ||
|
39
|
-
Enumerable.instance_methods.include?(method_sym)
|
40
|
-
# If any element of this class is read, we register this object as
|
41
|
-
# being read with the cache.
|
42
|
-
@store.cache.cache_read(self)
|
43
|
-
@data.send(method_sym, *args, &block)
|
44
|
-
elsif self.class::REWRITERS.include?(method_sym)
|
45
|
-
# Re-writers don't introduce any new elements. We just mark the object
|
46
|
-
# as written in the cache and call the class' method.
|
47
|
-
@store.cache.cache_write(self)
|
48
|
-
@data.send(method_sym, *args, &block)
|
49
|
-
elsif (alias_sym = self.class::ALIASES[method_sym])
|
50
|
-
@store.cache.cache_write(self)
|
51
|
-
send(alias_sym, *args, &block)
|
52
|
-
else
|
53
|
-
# Any method we don't know about must cause an error. A new class
|
54
|
-
# method needs to be added to the right bucket first.
|
55
|
-
raise NoMethodError.new("undefined method '#{method_sym}' for " +
|
56
|
-
"#{self.class}")
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def respond_to?(method_sym, include_private = false)
|
61
|
-
self.class::READERS.include?(method_sym) ||
|
62
|
-
Enumerable.instance_methods.include?(method_sym) ||
|
63
|
-
self.class::REWRITERS.include?(method_sym) ||
|
64
|
-
super
|
65
|
-
end
|
66
|
-
|
67
|
-
# Equivalent to Class::==
|
68
|
-
# This method is just a reader but also part of BasicObject. Hence
|
69
|
-
# BasicObject::== would be called instead of method_missing.
|
70
|
-
def ==(obj)
|
71
|
-
@store.cache.cache_read(self)
|
72
|
-
@data == obj
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|