lib/perobs/ObjectBase.rb in perobs-1.1.0 vs lib/perobs/ObjectBase.rb in perobs-2.0.0

- old
+ new

@@ -30,39 +30,111 @@ module PEROBS # This class is used to replace a direct reference to another Ruby object by # the Store ID. This makes object disposable by the Ruby garbage collector # since it's no longer referenced once it has been evicted from the - # PEROBS::Store cache. - class POReference < Struct.new(:id) + # PEROBS::Store cache. The POXReference objects function as a transparent + # proxy for the objects they are referencing. + class POXReference < BasicObject - # Textual dump for debugging purposes - # @return [String] - def inspect - "@#{id}" + attr_reader :store, :id + + def initialize(store, id) + super() + @store = store + @id = id end + # Proxy all calls to unknown methods to the referenced object. + def method_missing(method_sym, *args, &block) + unless (obj = _referenced_object) + raise ::RuntimeError, "Internal consistency error. No object with " + + "ID #{@id} found in the store" + end + if obj.respond_to?(:is_poxreference?) + raise ::RuntimeError, + "POXReference that references a POXReference found" + end + obj.send(method_sym, *args, &block) + end + + # Proxy all calls to unknown methods to the referenced object. Calling + # respond_to?(:is_poxreference?) is the only reliable way to find out if + # the object is a POXReference or not as pretty much every method call is + # proxied to the referenced object. + def respond_to?(method_sym, include_private = false) + (method_sym == :is_poxreference?) || + _referenced_object.respond_to?(method_sym, include_private) + end + + # Just for completeness. We don't want to be caught lying. + def is_poxreference? + true + end + + # @return [ObjectBase] Return the referenced object. This method should + # not be used outside of the PEROBS library. Leaked references can cause + # data corruption. + def _referenced_object + @store.object_by_id(@id) + end + + # BasicObject provides a ==() method that prevents method_missing from + # being called. So we have to pass the call manually to the referenced + # object. + # @param obj object to compare this object with. + def ==(obj) + _referenced_object == obj + end + + # Shortcut to access the _id() method of the referenced object. + def _id + @id + end + end + # This class is used to serialize the POXReference objects. It only holds + # the ID of the referenced Object. + class POReference < Struct.new(:id) + end + # Base class for all persistent objects. It provides the functionality # common to all classes of persistent objects. class ObjectBase attr_reader :_id, :store - # Create a new PEROBS::ObjectBase object. + # New PEROBS objects must always be created by calling # Store.new(). + # PEROBS users should never call this method or equivalents of derived + # methods directly. def initialize(store) @store = store + unless @store.object_creation_in_progress + raise ::RuntimeError, + "All PEROBS objects must exclusively be created by calling " + + "Store.new(). Never call the object constructor directly." + end @_id = @store.db.new_id @_stash_map = nil # Let the store know that we have a modified object. @store.cache.cache_write(self) end + public + + # This method can be overloaded by derived classes to do some massaging on + # the data after it has been restored from the database. This could either + # be some sanity check or code to migrate the object from one version to + # another. + def post_restore + end + # Two objects are considered equal if their object IDs are the same. def ==(obj) + return false unless obj.is_a?(ObjectBase) obj && @_id == obj._id end # Write the object into the backing store database. def _sync @@ -83,15 +155,16 @@ # Read the object from database. db_obj = store.db.get_object(id) klass = store.class_map.id_to_class(db_obj['class_id']) # Call the constructor of the specified class. - obj = Object.const_get(klass).new(store) + obj = store.construct_po(Object.const_get(klass)) # The object gets created with a new ID by default. We need to restore # the old one. obj._change_id(id) obj._deserialize(db_obj['data']) + obj.post_restore obj end # Restore the object state from the storage back-end. @@ -138,30 +211,11 @@ # Library internal method. Do not use outside of this library. # @private def _change_id(id) # Unregister the object with the old ID from the write cache to prevent # cache corruption. The objects are index by ID in the cache. - store.cache.unwrite(self) + @store.cache.unwrite(self) @_id = id - end - - private - - def _dereferenced(v) - v.is_a?(POReference) ? @store.object_by_id(v.id) : v - end - - def _referenced(obj) - if obj.is_a?(ObjectBase) - # The obj is a reference to another persistent object. Store the ID - # of that object in a POReference object. - if @store != obj.store - raise ArgumentError, 'The referenced object is not part of this store' - end - POReference.new(obj._id) - else - obj - end end end end