On Tuesday, February 3, 2015 at 10:19:04 AM UTC-5, Brian Kotek wrote:
> Hmm, just to add, I wouldn't be trying to do this in postLoad(). Shouldn't you be using postInsert()/postUpdate()?
Thanks for the feedback, Brian. I am following a similar pattern you mentioned.
Department.cfc
`component persistent="true" table="vw_hier_departments" extends="models.AuditableEntity" implements="models.IAuditable,models.IValidatable"`
There are actions happening on postLoad() and postInsert()/postUpdate(). On postLoad(), I am getting the current state of the entity. When postInsert()/postUpdate() are invoked, I then get that state of the entity. I then compare the changes between the mementos and then log them accordingly. One thing I added to AuditableEntity.cfc is a non-persistent property `intentToPersist` which is set manually in the code. Its purpose is to flag that I have loaded this eneity and I intend to modify it and persist changes. The reason for this is I do not want postLoad() to always make a copy of the memento each time an entity is loaded. That creates a lot of overhead on entities I don't plan on persisting. So what I typically do is this:
var oCenter = entityLoadByPK('Center', centerID);
// entity did not exist already
if (!structKeyExists(local, 'oCenter')) {
oCenter = entityNew('Center');
}
oCenter.setIntentToPersist(true);
AuditableEntity.cfc
-------------------
component mappedsuperclass="true" persistent="false" extends="models.BaseEntity" accessors="true" {
property name="intentToPersist" persistent="false" type="boolean" default="false";
variables.initialMemento = {};
variables.modifiedMemento = {};
/**
* Force the subclass to implement the proper interface
*/
public any function init () {
if (!isInstanceOf(this, "models.IAuditable")) {
throw (message="Entity #ucase(getEntityname())# must implement the following interface: models.IAuditable");
}
var _this = super.init();
return _this;
}
/**
* When set to <tt>true</tt>, this will invoke postLoad() (without reloading the entity)
* @output false
* @return void
*/
public void function setIntentToPersist (required boolean doPersist) {
intentToPersist = doPersist;
if (doPersist) {
postLoad();
}
}
public void function postLoad () {
if (ormGetSession().isTransactionInProgress() || getIntentToPersist()) {
initialMemento = copyMemento();
}
}
public void function postUpdate() {
modifiedMemento = copyMemento();
var changedData = getChanges(initialMemento, modifiedMemento);
var data = {memento = cleanMemento(modifiedMemento), changedData = changedData};
var entityData = {
id = getIdentityValue(),
table = getTableName(),
isDeleted = arrayFindNoCase(getPersistedProperties(), "isDeleted") && getIsDeleted()
};
// HACK: for whatever reason, this entity will not persist in the current session
// so putting it in a thread and watching for exceptions is the workaround
thread name="tAuditLogUpdate#createUUID()#" action="run" entity=local.entityData rowData=local.data {
transaction {
oAuditLog = entityNew('AuditLog');
oAuditLog.setRowID(
attributes.entity.id);
oAuditLog.setTableName(attributes.entity.table);
oAuditLog.setRowData(attributes.rowData.memento);
if (attributes.entity.isDeleted) {
// Record has been soft deleted
oAuditLog.setChangeType('softdelete');
} else {
// Normal Update
oAuditLog.setChangeType('update');
}
if (structCount(attributes.rowData.changedData) > 0) {
oAuditLog.setChangeNotesFromStruct(attributes.rowData.changedData);
}
entitySave(oAuditLog);
}
}
thread action="join";
checkThreads(cfthread);
}
...
}