perobs 2.1.1 → 2.2.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 +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
|
-
|