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.
-
#update ⇒ Object
Updates this domain object in the #database.
-
#validate ⇒ 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.
275 276 277 |
# File 'lib/caruby/database/persistable.rb', line 275 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.
191 192 193 194 195 196 197 198 199 200 |
# File 'lib/caruby/database/persistable.rb', line 191 def add_lazy_loader(loader, attributes=nil) # guard against invalid call if identifier.nil? then Jinx.fail(ValidationError, "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.
337 338 339 |
# File 'lib/caruby/database/persistable.rb', line 337 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.
161 162 163 |
# File 'lib/caruby/database/persistable.rb', line 161 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.
166 167 168 169 170 171 172 173 174 |
# File 'lib/caruby/database/persistable.rb', line 166 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.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/caruby/database/persistable.rb', line 386 def copy_volatile_attributes(other) pas = self.class.volatile_nondomain_attributes return if pas.empty? logger.debug { "Merging volatile attributes #{pas.to_series} from #{other.qp} into #{qp}..." } 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. Jinx.fail(DatabaseError, "Can't copy #{other} to #{self} with different identifier") end end 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 Jinx.fail(ValidationError, "#{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.
245 246 247 248 249 250 251 |
# File 'lib/caruby/database/persistable.rb', line 245 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.
238 239 240 |
# File 'lib/caruby/database/persistable.rb', line 238 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
341 342 343 344 345 346 347 |
# File 'lib/caruby/database/persistable.rb', line 341 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.
372 373 374 375 376 377 378 379 380 |
# File 'lib/caruby/database/persistable.rb', line 372 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.
130 131 132 |
# File 'lib/caruby/database/persistable.rb', line 130 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.
209 210 211 212 213 214 215 216 217 218 |
# File 'lib/caruby/database/persistable.rb', line 209 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.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/caruby/database/persistable.rb', line 139 def merge_into_snapshot(other) if @snapshot.nil? then Jinx.fail(ValidationError, "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.
225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/caruby/database/persistable.rb', line 225 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.
322 323 324 325 326 327 328 329 330 |
# File 'lib/caruby/database/persistable.rb', line 322 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.
280 281 282 |
# File 'lib/caruby/database/persistable.rb', line 280 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.
293 294 295 296 297 298 299 300 |
# File 'lib/caruby/database/persistable.rb', line 293 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.
125 126 127 |
# File 'lib/caruby/database/persistable.rb', line 125 def take_snapshot @snapshot = value_hash(self.class.updatable_attributes) 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 ⇒ 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 without an identifer is valid if it contains a non-nil value for each mandatory attribute. Objects which have an identifier or have already been validated are skipped.
A Persistable class should not override this method, but override the private #validate_local method instead.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/caruby/database/persistable.rb', line 263 def validate if identifier.nil? 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 |