perobs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = Cache.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/Store'
29
+
30
+ module PEROBS
31
+
32
+ # The Cache provides two functions for the PEROBS Store. It keeps some
33
+ # amount of objects in memory to substantially reduce read access latencies.
34
+ # It # also stores a list of objects that haven't been synced to the
35
+ # permanent store yet to accelerate object writes.
36
+ class Cache
37
+
38
+ # Create a new Cache object.
39
+ # @param bits [Fixnum] Number of bits for the cache index. This parameter
40
+ # heavilty affects the performance and memory consumption of the
41
+ # cache.
42
+ def initialize(bits = 16)
43
+ @bits = bits
44
+ # This mask is used to access the _bits_ least significant bits of the
45
+ # object ID.
46
+ @mask = 2 ** bits - 1
47
+ # Initialize the read and write cache
48
+ reset
49
+ end
50
+
51
+ # Add an PEROBS::Object to the read cache.
52
+ # @param obj [PEROBS::ObjectBase]
53
+ def cache_read(obj)
54
+ @reads[index(obj)] = obj
55
+ end
56
+
57
+ # Add a PEROBS::Object to the write cache.
58
+ # @param obj [PEROBS::ObjectBase]
59
+ def cache_write(obj)
60
+ if @transaction_stack.empty?
61
+ idx = index(obj)
62
+ if (old_obj = @writes[idx]) && old_obj._id != obj._id
63
+ # There is another old object using this cache slot. Before we can
64
+ # re-use the slot, we need to sync it to the permanent storage.
65
+ old_obj._sync
66
+ end
67
+ @writes[idx] = obj
68
+ else
69
+ # When a transaction is active, we don't have a write cache. The read
70
+ # cache is used to speed up access to recently used objects.
71
+ cache_read(obj)
72
+ # Push the reference of the modified object into the write buffer for
73
+ # this transaction level.
74
+ unless @transaction_stack.last.include?(obj)
75
+ @transaction_stack.last << obj
76
+ end
77
+ end
78
+ end
79
+
80
+ # Remove an object from the write cache. This will prevent a modified
81
+ # object from being written to the back-end store.
82
+ def unwrite(obj)
83
+ if @transaction_stack.empty?
84
+ idx = index(obj)
85
+ if (old_obj = @writes[idx]).nil? || old_obj._id != obj._id
86
+ raise RuntimeError, "Object to unwrite is not in cache"
87
+ end
88
+ @writes[idx] = nil
89
+ else
90
+ unless @transaction_stack.last.include?(obj)
91
+ raise RuntimeError, 'unwrite failed'
92
+ end
93
+ @transaction_stack.last.delete(obj)
94
+ end
95
+ end
96
+
97
+ # Return the PEROBS::Object with the specified ID or nil if not found.
98
+ # @param id [Fixnum or Bignum] ID of the cached PEROBS::ObjectBase
99
+ def object_by_id(id)
100
+ idx = id & @mask
101
+ # The index is just a hash. We still need to check if the object IDs are
102
+ # actually the same before we can return the object.
103
+ if (obj = @writes[idx]) && obj._id == id
104
+ # The object was in the write cache.
105
+ return obj
106
+ elsif (obj = @reads[idx]) && obj._id == id
107
+ # The object was in the read cache.
108
+ return obj
109
+ end
110
+
111
+ nil
112
+ end
113
+
114
+ # Flush all pending writes to the persistant storage back-end.
115
+ def flush
116
+ @writes.each { |w| w._sync if w }
117
+ @writes = ::Array.new(2 ** @bits)
118
+ end
119
+
120
+ # Returns true if the Cache is currently handling a transaction, false
121
+ # otherwise.
122
+ # @return [true/false]
123
+ def in_transaction?
124
+ !@transaction_stack.empty?
125
+ end
126
+
127
+ # Tell the cache to start a new transaction. If no other transaction is
128
+ # active, the write cached is flushed before the transaction is started.
129
+ def begin_transaction
130
+ if @transaction_stack.empty?
131
+ # This is the top-level transaction. Flush the write buffer to save
132
+ # the current state of all objects.
133
+ flush
134
+ else
135
+ @transaction_stack.last.each do |o|
136
+ o._stash(@transaction_stack.length - 1)
137
+ end
138
+ end
139
+ # Push a transaction buffer onto the transaction stack. This buffer will
140
+ # hold a reference to all objects modified during this transaction.
141
+ @transaction_stack.push(::Array.new)
142
+ end
143
+
144
+ # Tell the cache to end the currently active transaction. All write
145
+ # operations of the current transaction will be synced to the storage
146
+ # back-end.
147
+ def end_transaction
148
+ case @transaction_stack.length
149
+ when 0
150
+ raise RuntimeError, 'No ongoing transaction to end'
151
+ when 1
152
+ # All transactions completed successfully. Write all modified objects
153
+ # into the backend storage.
154
+ @transaction_stack.pop.each { |o| o._sync }
155
+ else
156
+ # A nested transaction completed successfully. We add the list of
157
+ # modified objects to the list of the enclosing transaction.
158
+ transactions = @transaction_stack.pop
159
+ # Merge the two lists
160
+ @transaction_stack.push(@transaction_stack.pop + transactions)
161
+ # Ensure that each object is only included once in the list.
162
+ @transaction_stack.last.uniq!
163
+ end
164
+ end
165
+
166
+ # Tell the cache to abort the currently active transaction. All modified
167
+ # objects will be restored from the storage back-end to their state before
168
+ # the transaction started.
169
+ def abort_transaction
170
+ if @transaction_stack.empty?
171
+ raise RuntimeError, 'No ongoing transaction to abort'
172
+ end
173
+ @transaction_stack.pop.each { |o| o._restore(@transaction_stack.length) }
174
+ end
175
+
176
+ # Clear all cached entries. You must call flush before calling this
177
+ # method. Otherwise unwritten objects will be lost.
178
+ def reset
179
+ # The read and write caches are Arrays. We use the _bits_ least
180
+ # significant bits of the PEROBS::ObjectBase ID to select the index in
181
+ # the read or write cache Arrays.
182
+ @reads = ::Array.new(2 ** @bits)
183
+ @writes = ::Array.new(2 ** @bits)
184
+ @transaction_stack = []
185
+ end
186
+
187
+ # Don't include the cache buffers in output of other objects that
188
+ # reference Cache.
189
+ def inspect
190
+ end
191
+
192
+ private
193
+
194
+ def index(obj)
195
+ obj._id & @mask
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = DataBase.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 'time'
29
+ require 'json'
30
+ require 'json/add/core'
31
+ require 'json/add/struct'
32
+ require 'yaml'
33
+ require 'fileutils'
34
+
35
+ require 'perobs/ObjectBase'
36
+
37
+ module PEROBS
38
+
39
+ # Base class for all storage back-ends.
40
+ class DataBase
41
+
42
+ def initialize(serializer = :json)
43
+ @serializer = serializer
44
+ end
45
+
46
+ # Serialize the given object using the object serializer.
47
+ # @param obj [ObjectBase] Object to serialize
48
+ # @return [String] Serialized version
49
+ def serialize(obj)
50
+ begin
51
+ case @serializer
52
+ when :marshal
53
+ Marshal.dump(obj)
54
+ when :json
55
+ obj.to_json
56
+ when :yaml
57
+ YAML.dump(obj)
58
+ end
59
+ rescue => e
60
+ raise RuntimeError,
61
+ "Cannot serialize object as #{@serializer}: #{e.message}"
62
+ end
63
+ end
64
+
65
+ # De-serialize the given String into a Ruby object.
66
+ # @param raw [String]
67
+ # @return [Hash] Deserialized version
68
+ def deserialize(raw)
69
+ begin
70
+ case @serializer
71
+ when :marshal
72
+ Marshal.load(raw)
73
+ when :json
74
+ JSON.parse(raw, :create_additions => true)
75
+ when :yaml
76
+ YAML.load(raw)
77
+ end
78
+ rescue => e
79
+ raise RuntimeError,
80
+ "Cannot de-serialize object with #{@serializer} parser: " +
81
+ e.message
82
+ end
83
+ end
84
+
85
+ # Generate a new unique ID. It uses random numbers between 0 and 2**64 -
86
+ # 1. Deriving classes can overwrite this method. They must implement a
87
+ # include? method.
88
+ # @return [Fixnum or Bignum]
89
+ def new_id
90
+ begin
91
+ # Generate a random number. It's recommended to not store more than
92
+ # 2**62 objects in the same store.
93
+ id = rand(2**64)
94
+ # Ensure that we don't have already another object with this ID.
95
+ end while include?(id)
96
+
97
+ id
98
+ end
99
+
100
+ private
101
+
102
+ # Ensure that we have a directory to store the DB items.
103
+ def ensure_dir_exists(dir)
104
+ unless Dir.exists?(dir)
105
+ begin
106
+ Dir.mkdir(dir)
107
+ rescue IOError => e
108
+ raise IOError, "Cannote create DB directory '#{dir}': #{e.message}"
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,171 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = FileSystemDB.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 'time'
29
+ require 'json'
30
+ require 'json/add/core'
31
+ require 'json/add/struct'
32
+ require 'yaml'
33
+ require 'fileutils'
34
+
35
+ require 'perobs/DataBase'
36
+ require 'perobs/ObjectBase'
37
+
38
+ module PEROBS
39
+
40
+ # This class provides a filesytem based database store for objects.
41
+ class FileSystemDB < DataBase
42
+
43
+ @@Extensions = {
44
+ :marshal => '.mshl',
45
+ :json => '.json',
46
+ :yaml => '.yml'
47
+ }
48
+
49
+ # Create a new FileSystemDB object. This will create a DB with the given
50
+ # name. A database will live in a directory of that name.
51
+ # @param db_name [String] name of the DB directory
52
+ # @param options [Hash] options to customize the behavior. Currently only
53
+ # the following option is supported:
54
+ # :serializer : Can be :marshal, :json, :yaml
55
+ def initialize(db_name, options = {})
56
+ super(options[:serializer] || :json)
57
+ @db_dir = db_name
58
+
59
+ # Create the database directory if it doesn't exist yet.
60
+ ensure_dir_exists(@db_dir)
61
+ end
62
+
63
+ # Return true if the object with given ID exists
64
+ # @param id [Fixnum or Bignum]
65
+ def include?(id)
66
+ File.exists?(object_file_name(id))
67
+ end
68
+
69
+ # Store the given object into the filesystem.
70
+ # @param obj [Hash] Object as defined by PEROBS::ObjectBase
71
+ def put_object(obj, id)
72
+ File.write(object_file_name(id), serialize(obj))
73
+ end
74
+
75
+ # Load the given object from the filesystem.
76
+ # @param id [Fixnum or Bignum] object ID
77
+ # @return [Hash] Object as defined by PEROBS::ObjectBase
78
+ def get_object(id)
79
+ begin
80
+ raw = File.read(file_name = object_file_name(id))
81
+ rescue => e
82
+ raise RuntimeError, "Error in #{file_name}: #{e.message}"
83
+ end
84
+ deserialize(raw)
85
+ end
86
+
87
+ # This method must be called to initiate the marking process.
88
+ def clear_marks
89
+ @mark_start = Time.now
90
+ # The filesystem stores access times with second granularity. We need to
91
+ # wait 1 sec. to ensure that all marks are noticeable.
92
+ sleep(1)
93
+ end
94
+
95
+ # Permanently delete all objects that have not been marked. Those are
96
+ # orphaned and are no longer referenced by any actively used object.
97
+ def delete_unmarked_objects
98
+ Dir.glob(File.join(@db_dir, '*')) do |dir|
99
+ next unless Dir.exists?(dir)
100
+
101
+ Dir.glob(File.join(dir, '*')) do |file|
102
+ if File.atime(file) <= @mark_start
103
+ File.delete(file)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # Mark an object.
110
+ # @param id [Fixnum or Bignum] ID of the object to mark
111
+ def mark(id)
112
+ FileUtils.touch(object_file_name(id))
113
+ end
114
+
115
+ # Check if the object is marked.
116
+ # @param id [Fixnum or Bignum] ID of the object to check
117
+ def is_marked?(id)
118
+ File.atime(object_file_name(id)) > @mark_start
119
+ end
120
+
121
+ # Check if the stored object is syntactically correct.
122
+ # @param id [Fixnum/Bignum] Object ID
123
+ # @param repair [TrueClass/FalseClass] True if an repair attempt should be
124
+ # made.
125
+ # @return [TrueClass/FalseClass] True if the object is OK, otherwise
126
+ # false.
127
+ def check(id, repair)
128
+ file_name = object_file_name(id)
129
+ unless File.exists?(file_name)
130
+ $stderr.puts "Object file for ID #{id} does not exist"
131
+ return false
132
+ end
133
+
134
+ begin
135
+ get_object(id)
136
+ rescue => e
137
+ $stderr.puts "Cannot read object file #{file_name}: #{e.message}"
138
+ return false
139
+ end
140
+
141
+ true
142
+ end
143
+
144
+ private
145
+
146
+ # Ensure that we have a directory to store the DB items.
147
+ def ensure_dir_exists(dir)
148
+ unless Dir.exists?(dir)
149
+ begin
150
+ Dir.mkdir(dir)
151
+ rescue IOError => e
152
+ raise IOError, "Cannote create DB directory '#{dir}': #{e.message}"
153
+ end
154
+ end
155
+ end
156
+
157
+ # Determine the file name to store the object. The object ID determines
158
+ # the directory and file name inside the store.
159
+ # @param id [Fixnum or Bignum] ID of the object
160
+ def object_file_name(id)
161
+ hex_id = "%016X" % id
162
+ dir = hex_id[0..1]
163
+ ensure_dir_exists(File.join(@db_dir, dir))
164
+
165
+ File.join(@db_dir, dir, hex_id + @@Extensions[@serializer])
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
@@ -0,0 +1,175 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = Hash.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/ObjectBase'
29
+
30
+ module PEROBS
31
+
32
+ # A Hash that is transparently persisted in the back-end storage. It is very
33
+ # similar to the Ruby built-in Hash class but has some additional
34
+ # limitations. The hash key must always be a String.
35
+ class Hash < ObjectBase
36
+
37
+ # Create a new PersistentHash object.
38
+ # @param store [Store] The Store this hash is stored in
39
+ # @param default [Any] The default value that is returned when no value is
40
+ # stored for a specific key.
41
+ def initialize(store, default = nil)
42
+ super(store)
43
+ @default = nil
44
+ @data = {}
45
+ end
46
+
47
+ # Retrieves the value object corresponding to the
48
+ # key object. If not found, returns the default value.
49
+ def [](key)
50
+ #unless key.is_a?(String)
51
+ # raise ArgumentError, 'The Hash key must be of type String'
52
+ #end
53
+ _dereferenced(@data.include?(key) ? @data[key] : @default)
54
+ end
55
+
56
+ # Associates the value given by value with the key given by key.
57
+ # @param key [String] The key
58
+ # @param value [Any] The value to store
59
+ def []=(key, value)
60
+ #unless key.is_a?(String)
61
+ # raise ArgumentError, 'The Hash key must be of type String'
62
+ #end
63
+ @data[key] = _referenced(value)
64
+ @store.cache.cache_write(self)
65
+
66
+ value
67
+ end
68
+
69
+ # Equivalent to Hash::clear
70
+ def clear
71
+ @store.cache.cache_write(self)
72
+ @data.clear
73
+ end
74
+
75
+ # Equivalent to Hash::delete
76
+ def delete(key)
77
+ @store.cache.cache_write(self)
78
+ @data.delete(key)
79
+ end
80
+
81
+ # Equivalent to Hash::delete_if
82
+ def delete_if
83
+ @store.cache.cache_write(self)
84
+ @data.delete_if do |k, v|
85
+ yield(k, _dereferenced(v))
86
+ end
87
+ end
88
+
89
+ # Equivalent to Hash::each
90
+ def each
91
+ @data.each do |k, v|
92
+ yield(k, _dereferenced(v))
93
+ end
94
+ end
95
+
96
+ # Equivalent to Hash::each_key
97
+ def each_key
98
+ @data.each_key { |k| yield(k) }
99
+ end
100
+
101
+ # Equivalent to Hash::each_value
102
+ def each_value
103
+ @data.each_value do |v|
104
+ yield(_dereferenced(v))
105
+ end
106
+ end
107
+
108
+ # Equivalent to Hash::empty?
109
+ def emtpy?
110
+ @data.empty?
111
+ end
112
+
113
+ # Equivalent to Hash::has_key?
114
+ def has_key?(key)
115
+ @data.has_key?(key)
116
+ end
117
+ alias include? has_key?
118
+ alias key? has_key?
119
+ alias member? has_key?
120
+
121
+ # Equivalent to Hash::keys
122
+ def keys
123
+ @data.keys
124
+ end
125
+
126
+ # Equivalent to Hash::length
127
+ def length
128
+ @data.length
129
+ end
130
+ alias size length
131
+
132
+ # Equivalent to Hash::map
133
+ def map
134
+ @data.map do |k, v|
135
+ yield(k, _dereferenced(v))
136
+ end
137
+ end
138
+
139
+ # Equivalent to Hash::values
140
+ def values
141
+ @data.values.map { |v| _dereferenced(v) }
142
+ end
143
+
144
+ # Return a list of all object IDs of all persistend objects that this Hash
145
+ # is referencing.
146
+ # @return [Array of Fixnum or Bignum] IDs of referenced objects
147
+ def _referenced_object_ids
148
+ @data.each_value.select { |v| v && v.is_a?(POReference) }.map { |o| o.id }
149
+ end
150
+
151
+ # This method should only be used during store repair operations. It will
152
+ # delete all referenced to the given object ID.
153
+ # @param id [Fixnum/Bignum] targeted object ID
154
+ def _delete_reference_to_id(id)
155
+ @data.delete_if { |k, v| v && v.is_a?(POReference) && v.id == id }
156
+ end
157
+
158
+ # Restore the persistent data from a single data structure.
159
+ # This is a library internal method. Do not use outside of this library.
160
+ # @param data [Hash] the actual Hash object
161
+ # @private
162
+ def _deserialize(data)
163
+ @data = data
164
+ end
165
+
166
+ private
167
+
168
+ def _serialize
169
+ @data
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+