Transfer ORM Event Model Examples - BeforeCreate and BeforeUpdate
The topic of how one might track created and modified dates came up on the transfer list today and Brian Kotek mentioned how he uses the transfer event model to track changes when creating or updating Transfer objects. I thought it might be interesting to see just how easy it would be to implement his approach.
The transfer event model is an implementation of the Observer/Observable pattern in which one object registers itself to listen for events from another object. When an event occurs on the observable object, the observer (a.k.a listener, or subscriber) is notified. The transfer event model currently supports seven events, each of which require the observer to implement a specific "listener" method. Here's how I decided to implement Brian's approach.
CAVEAT LECTOR I rushed this post out without thinking through the implementation. This is an incorrect and possibly dangerous way to use the transfer event model. Please see Bob Silverberg's post on this topic.
I define a configure() method within all of my transfer decorators. Configure() is invoked when a transfer object is instantiated. This makes it a perfect candidate to add additional behaviour by registering the Transfer object to listen for specific events. In this example I'll setup the transfer object to listen for the BeforeCreate and BeforeUpdate events.
<!--- register for transfer events --->
<cfset getTransfer().addBeforeCreateObserver(this)>
<cfset getTransfer().addBeforeUpdateObserver(this)>
</cffunction>
Once registered for these events, the Transfer object must implement the following methods.
<cfargument name="event" hint="The event object" type="transfer.com.events.TransferEvent" required="Yes">
<cfset setCreatedDate(now())>
</cffunction>
<cffunction name="actionBeforeUpdateTransferEvent" access="public" returntype="void" output="false" hint="I set the created date before I am persisted for the first time.">
<cfargument name="event" hint="The event object" type="transfer.com.events.TransferEvent" required="Yes">
<cfset setModifiedDate(now())>
</cffunction>
Assuming that we have two properties on our Transfer object for CreatedDate and ModifiedDate, setting those values is now nicely de-coupled from a service or controller layers. Which is one of the reasons I really like using transfer decorators.
Additionally, if you are in the habit of creating these properties on all your business objects, you could place these methods in a base decorator and call super.configure() within the configure() method of your transfer decorator.



Your object would have code very similar to what Paul has listed above, for example, in your BeforeCreateObserver.cfc you would have:
(code example below - I hope the formatting works out ok)
<cffunction name="actionBeforeCreateTransferEvent" access="public" returntype="void" output="false" hint="I set the created date before I am persisted for the first time.">
<cfargument name="event" hint="The event object" type="transfer.com.events.TransferEvent" required="Yes">
<cfif StructKeyExists(arguments.event.getTransferObject(),"setCreatedDate")>
<cfset arguments.event.getTransferObject().setCreatedDate(now())/>
</cfif>
</cffunction>
You then have to add this observer to Transfer, which can be done pro grammatically after you create the Transfer factory, or via another object in Coldspring using lazy-init="false" (similar to what Brian does with his bean injector).
After that's all set up any object that has a setCreatedDate() method will automatically have the date set before it is persisted in the database.
I guess I didn't have to reformat my code! here's the example again:
<cffunction name="actionBeforeCreateTransferEvent" access="public" returntype="void" output="false" hint="I set the created date before I am persisted for the first time.">
<cfargument name="event" hint="The event object" type="transfer.com.events.TransferEvent" required="Yes" />
<cfif StructKeyExists(arguments.event.getTransferObject(),"setCreatedDate")>
<cfset arguments.event.getTransferObject().setCreatedDate(now()) />
</cfif>
</cffunction>
<cffunction name="actionBeforeUpdateTransferEvent" hint="Do something on the new object" access="public" returntype="void" output="false">
<cfargument name="event" hint="" type="transfer.com.events.TransferEvent" required="Yes">
<cfset var to = arguments.event.getTransferObject() />
<cfif to.getIsDirty()
and StructKeyExists(to, 'setModifierID')
and StructKeyExists(to, 'setDateModified')>
<cfif StructKeyExists(variables.instance, 'sessionFacade') and getSessionFacade().exists('User')>
<cfset to.setModifierID(getSessionFacade().getUser().getID()) />
<cfelse>
<cfset to.setModifierID(variables.instance.systemAccountID) />
</cfif>
<cfset to.setDateModified(Now()) />
</cfif>
</cffunction>
<cffunction name="actionBeforeCreateTransferEvent" hint="Do something on the new object" access="public" returntype="void" output="false">
<cfargument name="event" hint="" type="transfer.com.events.TransferEvent" required="Yes">
<cfset var to = arguments.event.getTransferObject() />
<cfif to.getIsDirty()
and StructKeyExists(to, 'setCreatorID')
and StructKeyExists(to, 'setDateCreated')>
<cfif StructKeyExists(variables.instance, 'sessionFacade') and getSessionFacade().exists('User')>
<cfset to.setCreatorID(getSessionFacade().getUser().getID()) />
<cfelse>
<cfset to.setCreatorID(variables.instance.systemAccountID) />
</cfif>
<cfset to.setDateCreated(Now()) />
</cfif>
<cfset actionBeforeUpdateTransferEvent(arguments.event) />
</cffunction>
I see how using a single separate "DateStamper" object as an event listener is preferable to my implementation.
For anyone following this, see Bob Silverberg's take on the subject for a detailed example of how you can do this more elegantly,..
http://www.silverwareconsulting.com/index.cfm/2008...