May 21
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.<cffunction name="configure" access="public" output="false" returntype="void">
<!--- 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.
<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">
<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.
May 21, 2008 at 8:55 AM You can also accomplish this by creating an a couple of objects to act as your BeforeCreate and BeforeUpdate observers, which would make it unnecessary to add code into each decorator. In fact, using this method you don't even need decorators.
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)
&lt;cffunction name="actionBeforeCreateTransferEvent" access="public" returntype="void" output="false" hint="I set the created date before I am persisted for the first time."&gt;
&lt;cfargument name="event" hint="The event object" type="transfer.com.events.TransferEvent" required="Yes"&gt;
&lt;cfif StructKeyExists(arguments.event.getTransferObject(),"setCreatedDate")&gt;
&lt;cfset arguments.event.getTransferObject().setCreatedDate(now())/&gt;
&lt;/cfif&gt;
&lt;/cffunction&gt;
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.
May 21, 2008 at 8:57 AM Ugh,
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>
May 21, 2008 at 10:26 AM Bob is right, you can set up one central (or more than one if you like) Observer and let it handle this, instead of having to set up each Decorator as an Observer (though that might be useful in some cases). I actually do something like this:
<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>
May 21, 2008 at 1:03 PM Gents,
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/5/21/My-Take-on-Transfer-ORM-Event-Model-Examples--BeforeCreate-Example
Nov 23, 2008 at 6:27 PM thanks