Module: CaRuby::Persistable
- Included in:
- Resource
- Defined in:
- lib/caruby/database/persistable.rb
Overview
The Persistable mixin adds persistance capability. Every instance which includes Persistable must respond to an overrided #database method.
Instance Attribute Summary collapse
-
#snapshot ⇒ {Symbol => Object}
readonly
The content value hash at the point of the last snapshot.
Class Method Summary collapse
-
.saved?(obj) ⇒ Boolean
Whether the given object(s) have an identifier.
-
.unsaved?(obj) ⇒ Boolean
Whether at least one of the given object(s) does not have an identifier.
Instance Method Summary collapse
-
#add_defaults_autogenerated ⇒ Object
Sets the default attribute values for this auto-generated domain object.
-
#add_lazy_loader(loader, attributes = nil) ⇒ Object
Lazy loads the attributes.
-
#autogenerated?(operation) ⇒ <Symbol>
Relaxes the #saved_attributes_to_fetch condition for a SCG as follows: * If the SCG status was updated from
Pending
toCollected
, then fetch the saved SCG event parameters. -
#changed?(attribute = nil) ⇒ Boolean
Returns whether this Persistable either doesn’t have a snapshot or has changed since the last snapshot.
-
#changed_attributes ⇒ <Symbol>
The attributes which differ between the #snapshot and current content.
-
#copy_volatile_attributes(other) ⇒ Object
Sets the CaRuby::Propertied#volatile_nondomain_attributes to the other fetched value, if different.
-
#create ⇒ Object
Creates this domain object in the #database.
-
#database ⇒ Database
Returns the data access mediator for this domain object.
-
#delete ⇒ Object
Deletes this domain object from the #database.
-
#do_without_lazy_loader { ... } ⇒ Object
Executes the given block with the database lazy loader disabled, if any.
-
#dump ⇒ Object
Wrap
Resource.dump
to disable the lazy-loader while printing. -
#ensure_exists ⇒ Object
Creates this domain object, if necessary.
- #fetch_autogenerated?(operation) ⇒ Boolean
-
#fetch_saved? ⇒ Boolean
Returns whether this domain object must be fetched to reflect the database state.
-
#fetched? ⇒ Boolean
Whether this Persistable has a #snapshot.
-
#find(opts = nil) ⇒ Object
Fetches this domain object from the #database.
-
#loadable_attributes ⇒ <Symbol>
Returns the attributes to load on demand.
-
#merge_into_snapshot(other) ⇒ Object
Merges the other domain object non-domain attribute values into this domain object’s snapshot, An existing snapshot value is replaced by the corresponding other attribute value.
-
#persistence_service ⇒ PersistenceService
The database application service for this Persistable.
-
#query(*path) ⇒ Object
Fetches the domain objects which match this template from the #database.
-
#remove_lazy_loader(attribute = nil) ⇒ Object
Disables lazy loading of the specified attribute.
-
#save ⇒ Object
(also: #store)
Saves this domain object in the #database.
-
#saved_attributes_to_fetch(operation) ⇒ <Symbol>
Returns this domain object’s attributes which must be fetched to reflect the database state.
-
#searchable? ⇒ Boolean
Whether this domain object has #searchable_attributes.
-
#searchable_attributes ⇒ <Symbol>
Returns the attributes to use for a search using this domain object as a template, determined as follows: * If this domain object has a non-nil primary key, then the primary key is the search criterion.
-
#take_snapshot ⇒ {Symbol => Object}
Captures the Persistable’s updatable attribute base values.
-
#updatable? ⇒ Boolean
Whether this domain object can be updated (default is true, subclasses can override).
-
#update ⇒ Object
Updates this domain object in the #database.
-
#validate(autogenerated = false) ⇒ Persistable
Validates this domain object and its #CaRuby::Propertied#unproxied_savable_template_attributes for consistency and completeness prior to a database create operation.
Instance Attribute Details
#snapshot ⇒ {Symbol => Object} (readonly)
Returns the content value hash at the point of the last snapshot.
11 12 13 |
# File 'lib/caruby/database/persistable.rb', line 11 def snapshot @snapshot end |
Class Method Details
.saved?(obj) ⇒ Boolean
Returns whether the given object(s) have an identifier.
15 16 17 18 19 20 21 22 23 |
# File 'lib/caruby/database/persistable.rb', line 15 def self.saved?(obj) if obj.nil_or_empty? then false elsif obj.collection? then obj.all? { |ref| saved?(ref) } else !!obj.identifier end end |
.unsaved?(obj) ⇒ Boolean
Returns whether at least one of the given object(s) does not have an identifier.
27 28 29 |
# File 'lib/caruby/database/persistable.rb', line 27 def self.unsaved?(obj) not (obj.nil_or_empty? or saved?(obj)) end |
Instance Method Details
#add_defaults_autogenerated ⇒ Object
Sets the default attribute values for this auto-generated domain object.
281 282 283 |
# File 'lib/caruby/database/persistable.rb', line 281 def add_defaults_autogenerated add_defaults_recursive end |
#add_lazy_loader(loader, attributes = nil) ⇒ Object
Lazy loads the attributes. If a block is given to this method, then the attributes are determined by calling the block with this Persistable as a parameter. Otherwise, the default attributes are the unfetched domain attributes.
Each of the attributes which does not already hold a non-nil or non-empty value will be loaded from the database on demand. This method injects attribute value initialization into each loadable attribute reader. The initializer is given by either the loader Proc argument. The loader takes two arguments, the target object and the attribute to load. If this Persistable already has a lazy loader, then this method is a no-op.
Lazy loading is disabled on an attribute after it is invoked on that attribute or when the attribute setter method is called.
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/caruby/database/persistable.rb', line 197 def add_lazy_loader(loader, attributes=nil) # guard against invalid call if identifier.nil? then raise ValidationError.new("Cannot add lazy loader to an unfetched domain object: #{self}") end # the attributes to lazy-load attributes ||= loadable_attributes return if attributes.empty? # define the reader and writer method overrides for the missing attributes pas = attributes.select { |pa| inject_lazy_loader(pa) } logger.debug { "Lazy loader added to #{qp} attributes #{pas.to_series}." } unless pas.empty? end |
#autogenerated?(operation) ⇒ <Symbol>
Relaxes the #saved_attributes_to_fetch condition for a SCG as follows:
-
If the SCG status was updated from
Pending
toCollected
, then fetch the saved SCG event parameters.
343 344 345 |
# File 'lib/caruby/database/persistable.rb', line 343 def autogenerated?(operation) operation == :update && status_changed_to_complete? ? EVENT_PARAM_ATTRS : super end |
#changed?(attribute = nil) ⇒ Boolean
Returns whether this Persistable either doesn’t have a snapshot or has changed since the last snapshot. This is a conservative condition test that returns false if there is no snaphsot for this Persistable and therefore no basis to determine whether the content changed. If the attribute parameter is given, then only that attribute is checked for a change. Otherwise, all attributes are checked.
167 168 169 |
# File 'lib/caruby/database/persistable.rb', line 167 def changed?(attribute=nil) @snapshot.nil? or not snapshot_equal_content?(attribute) end |
#changed_attributes ⇒ <Symbol>
Returns the attributes which differ between the #snapshot and current content.
172 173 174 175 176 177 178 179 180 |
# File 'lib/caruby/database/persistable.rb', line 172 def changed_attributes if @snapshot then ovh = value_hash(self.class.updatable_attributes) diff = @snapshot.diff(ovh) { |pa, v, ov| Jinx::Resource.value_equal?(v, ov) } diff.keys else self.class.updatable_attributes end end |
#copy_volatile_attributes(other) ⇒ Object
Sets the CaRuby::Propertied#volatile_nondomain_attributes to the other fetched value, if different.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# File 'lib/caruby/database/persistable.rb', line 392 def copy_volatile_attributes(other) pas = self.class.volatile_nondomain_attributes return if pas.empty? pas.each do |pa| val = send(pa) oval = other.send(pa) if val.nil? then # Overwrite the current attribute value. set_property_value(pa, oval) logger.debug { "Set #{qp} volatile #{pa} to the fetched #{other.qp} database value #{oval.qp}." } elsif oval != val and pa == :identifier then # If this error occurs, then there is a serious match-merge flaw. raise DatabaseError.new("Can't copy #{other} to #{self} with different identifier") end end logger.debug { "Merged auto-generated attribute values #{pas.to_series} from #{other.qp} into #{self}..." } end |
#create ⇒ Object
Creates this domain object in the #database.
74 75 76 |
# File 'lib/caruby/database/persistable.rb', line 74 def create database.create(self) end |
#database ⇒ Database
Returns the data access mediator for this domain object. Application #Jinx::Resource modules are required to override this method.
36 37 38 |
# File 'lib/caruby/database/persistable.rb', line 36 def database raise ValidationError.new("#{self} database is missing") end |
#delete ⇒ Object
Deletes this domain object from the #database.
113 114 115 |
# File 'lib/caruby/database/persistable.rb', line 113 def delete database.delete(self) end |
#do_without_lazy_loader { ... } ⇒ Object
Executes the given block with the database lazy loader disabled, if any.
251 252 253 254 255 256 257 |
# File 'lib/caruby/database/persistable.rb', line 251 def do_without_lazy_loader(&block) if database then database.lazy_loader.disable(&block) else yield end end |
#dump ⇒ Object
Wrap Resource.dump
to disable the lazy-loader while printing.
244 245 246 |
# File 'lib/caruby/database/persistable.rb', line 244 def dump do_without_lazy_loader { super } end |
#ensure_exists ⇒ Object
Creates this domain object, if necessary.
81 82 83 |
# File 'lib/caruby/database/persistable.rb', line 81 def ensure_exists database.ensure_exists(self) end |
#fetch_autogenerated?(operation) ⇒ Boolean
347 348 349 350 351 352 353 |
# File 'lib/caruby/database/persistable.rb', line 347 def fetch_autogenerated?(operation) # only fetch a create, not an update (note that subclasses can override this condition) operation == :update # Check for an attribute with a value that might need to be changed in order to # reflect the auto-generated database content. self.class.autogenerated_logical_dependent_attributes.select { |pa| not send(pa).nil_or_empty? } end |
#fetch_saved? ⇒ Boolean
Returns whether this domain object must be fetched to reflect the database state. This default implementation returns whether this domain object was created and there are any autogenerated attributes. Subclasses can override to relax or restrict the condition.
TODO - this method is no longeer used. Should it be? If not, remove here and in catissue subclasses.
378 379 380 381 382 383 384 385 386 |
# File 'lib/caruby/database/persistable.rb', line 378 def fetch_saved? # only fetch a create, not an update (note that subclasses can override this condition) return false if identifier # Check for an attribute with a value that might need to be changed in order to # reflect the auto-generated database content. ag_attrs = self.class.autogenerated_attributes return false if ag_attrs.empty? ag_attrs.any? { |pa| not send(pa).nil_or_empty? } end |
#fetched? ⇒ Boolean
Returns whether this Persistable has a #snapshot.
136 137 138 |
# File 'lib/caruby/database/persistable.rb', line 136 def fetched? !!@snapshot end |
#find(opts = nil) ⇒ Object
Fetches this domain object from the #database.
64 65 66 |
# File 'lib/caruby/database/persistable.rb', line 64 def find(opts=nil) database.find(self, opts) end |
#loadable_attributes ⇒ <Symbol>
Returns the attributes to load on demand. The base attribute list is given by the CaRuby::Propertied#loadable_attributes whose value is nil or empty. In addition, if this Persistable has more than one Domain::Dependency#owner_attributes and one is non-nil, then none of the owner attributes are loaded on demand, since there can be at most one owner and ownership cannot change.
215 216 217 218 219 220 221 222 223 224 |
# File 'lib/caruby/database/persistable.rb', line 215 def loadable_attributes pas = self.class.loadable_attributes.select { |pa| send(pa).nil_or_empty? } ownr_attrs = self.class.owner_attributes # If there is an owner, then variant owners are not loaded. if ownr_attrs.size > 1 and ownr_attrs.any? { |pa| not send(pa).nil_or_empty? } then pas - ownr_attrs else pas end end |
#merge_into_snapshot(other) ⇒ Object
Merges the other domain object non-domain attribute values into this domain object’s snapshot, An existing snapshot value is replaced by the corresponding other attribute value.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/caruby/database/persistable.rb', line 145 def merge_into_snapshot(other) if @snapshot.nil? then raise ValidationError.new("Cannot merge #{other.qp} content into #{qp} snapshot, since #{qp} does not have a snapshot.") end # the non-domain attribute => [target value, other value] difference hash delta = diff(other) # the difference attribute => other value hash, excluding nil other values dvh = delta.transform_value { |d| d.last } return if dvh.empty? logger.debug { "#{qp} differs from database content #{other.qp} as follows: #{delta.filter_on_key { |pa| dvh.has_key?(pa) }.qp}" } logger.debug { "Setting #{qp} snapshot values from other #{other.qp} values to reflect the database state: #{dvh.qp}..." } # update the snapshot from the other value to reflect the database state @snapshot.merge!(dvh) end |
#persistence_service ⇒ PersistenceService
Returns the database application service for this Persistable.
41 42 43 |
# File 'lib/caruby/database/persistable.rb', line 41 def persistence_service database.persistence_service(self.class) end |
#query(*path) ⇒ Object
Fetches the domain objects which match this template from the #database.
52 53 54 |
# File 'lib/caruby/database/persistable.rb', line 52 def query(*path) path.empty? ? database.query(self) : database.query(self, *path) end |
#remove_lazy_loader(attribute = nil) ⇒ Object
Disables lazy loading of the specified attribute. Lazy loaded is disabled for all attributes if no attribute is specified. This method is a no-op if this Persistable does not have a lazy loader.
231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/caruby/database/persistable.rb', line 231 def remove_lazy_loader(attribute=nil) if attribute.nil? then return self.class.domain_attributes.each { |pa| remove_lazy_loader(pa) } end # the modified accessor method reader, writer = self.class.property(attribute).accessors # remove the reader override disable_singleton_method(reader) # remove the writer override disable_singleton_method(writer) end |
#save ⇒ Object Also known as: store
Saves this domain object in the #database.
91 92 93 |
# File 'lib/caruby/database/persistable.rb', line 91 def save database.save(self) end |
#saved_attributes_to_fetch(operation) ⇒ <Symbol>
Returns this domain object’s attributes which must be fetched to reflect the database state. This default implementation returns the CaRuby::Propertied#autogenerated_logical_dependent_attributes if this domain object does not have an identifier, or an empty array otherwise. Subclasses can override to relax or restrict the condition.
328 329 330 331 332 333 334 335 336 |
# File 'lib/caruby/database/persistable.rb', line 328 def saved_attributes_to_fetch(operation) # only fetch a create, not an update (note that subclasses can override this condition) if operation.type == :create or operation.autogenerated? then # Filter the class saved fetch attributes for content. self.class.saved_attributes_to_fetch.select { |pa| not send(pa).nil_or_empty? } else Array::EMPTY_ARRAY end end |
#searchable? ⇒ Boolean
Returns whether this domain object has #searchable_attributes.
286 287 288 |
# File 'lib/caruby/database/persistable.rb', line 286 def searchable? not searchable_attributes.nil? end |
#searchable_attributes ⇒ <Symbol>
Returns the attributes to use for a search using this domain object as a template, determined as follows:
-
If this domain object has a non-nil primary key, then the primary key is the search criterion.
-
Otherwise, if this domain object has a secondary key and each key attribute value is not nil, then the secondary key is the search criterion.
-
Otherwise, if this domain object has an alternate key and each key attribute value is not nil, then the aklternate key is the search criterion.
299 300 301 302 303 304 305 306 |
# File 'lib/caruby/database/persistable.rb', line 299 def searchable_attributes key_props = self.class.primary_key_attributes return key_props if key_searchable?(key_props) key_props = self.class.secondary_key_attributes return key_props if key_searchable?(key_props) key_props = self.class.alternate_key_attributes return key_props if key_searchable?(key_props) end |
#take_snapshot ⇒ {Symbol => Object}
Captures the Persistable’s updatable attribute base values. The snapshot is subsequently accessible using the #snapshot method.
131 132 133 |
# File 'lib/caruby/database/persistable.rb', line 131 def take_snapshot @snapshot = value_hash(self.class.updatable_attributes) end |
#updatable? ⇒ Boolean
Returns whether this domain object can be updated (default is true, subclasses can override).
119 120 121 |
# File 'lib/caruby/database/persistable.rb', line 119 def updatable? true end |
#update ⇒ Object
Updates this domain object in the #database.
103 104 105 |
# File 'lib/caruby/database/persistable.rb', line 103 def update database.update(self) end |
#validate(autogenerated = false) ⇒ Persistable
Validates this domain object and its #CaRuby::Propertied#unproxied_savable_template_attributes for consistency and completeness prior to a database create operation. An object is valid if it contains a non-nil value for each mandatory attribute. Objects which have already been validated are skipped.
A Persistable class should not override this method, but override the private #validate_local method instead.
269 270 271 272 273 274 275 276 277 278 |
# File 'lib/caruby/database/persistable.rb', line 269 def validate(autogenerated=false) if (identifier.nil? or autogenerated) and not @validated then validate_local @validated = true end self.class.unproxied_savable_template_attributes.each do |pa| send(pa).enumerate { |dep| dep.validate } end self end |