COmanage Registry can track each change made to a Model through the use of Changelogs. When a record is modified, the old values are archived in order to provide an audit or change log of the modifications that were made. Changelogs are implemented using a custom Cake Behavior (ChangelogBehavior).
Changelog Behavior operates using a modified copy on write design. When a record is changed, the old values are copied to a new record, and then the original record is updated with the new values. There are two reasons for this design: it avoids massive rekeying when a record is updated (otherwise related Models would all need to be re-keyed to point to the new record), and because it's tricky within Cake to rewrite the record ID in the middle of an operation. (That is, for
/foos/edit/23, Cake really wants the item being edited to be #23, and not some new number.) It is simply easier to make the archived copy the new record.
This behavior can be modified using the model variable
$relinkToArchive. If a Model defines a related hasOne or hasMany Model in this variable, then ChangelogBehavior will relink the related model to the archive copy, as part of the
afterSave callback. This is useful for maintaining historical context, such as for Petition attribute definitions. If the Model relationship is belongsTo, a similar rewrite happens, but in
beforeSave and with the assumption that the parent model is what was changed. (The belongsTo relationship is used for when related Models are saved simultaneously as part of a saveAll or saveAssociated call.)
See also: Understanding Registry Deletion
Enabling Changelogs for a Model
ChangelogBehavior must execute before ContainableBehavior in order to properly filter results from contain'd finds. The default priority for behaviors is 10, so setting the priority to 5 will cause the ChangelogBehavior to execute first.
NormalizationBehavior should execute before ChangelogBehavior in order to clean up data before any save happens.
As with other Behaviors, the appropriate Model should define something like
In order to support Changelogs, a Changelog-enabled Model must define several fields. As described below, these fields are used to track changed records:
For performance reasons, model_id should be indexed.
$relinkToArchive should also be defined if needed, as described above.
When a hard delete must be performed, ChangelogBehavior can be initialized with the setting expunge set to true. This setting is also supported by StandardController, via the controller level setting
actor_identifier stores the username (identifier) of whoever made the change. This is a string rather than a foreign key into cm_co_people for several reasons:
- No need for foreign keys and Model relations, which must be added to each Changelog model, and are tricky to automatically create for tables created in
- Audit integrity is maintained even after an expunge.
- Some models are not attached to a CO, so tracking a CO Person as an actor doesn't quite make sense.
Extended Attributes are handled differently. They are treated as "extensions" of CoPersonRole, and archived copies are manually maintained by CoPersonRolesController.
What ChangelogBehavior Does
ChangelogBehavior intercepts find, save, and delete requests to transparently manage the Changelog records.
revisionis set to 0.
actor_identifieris set to the current username.
- The prior values are copied to a new row, and
model_idis set to point to the parent record.
If the Model defines related models in
$relinkToArchive, those related models will be re-keyed so that the related records point to the archived attribute instead of the current attribute. (By default, related models remain linked to the current attribute.)
deletedis set to true (effecting a soft delete), unless ChangelogBehavior is initialized with the setting expunge set to true (in which case a hard delete is performed).
- By default, a find that does not request a specific record ID will be modified to return the most current revision and to exclude deleted records.
A find with the
archivedset to true will return all records, not just those described above.
By passing the
revision, a find can request a specific revision for a record:
- If ChangelogBehavior is initialized with the setting expunge set to true, all records will be returned, not just those described above.