Orange is my favorite color

I have been tempted, and even used successfully, the Transfer.clone() method to duplicate an existing Transfer object. But Mr. Corfield told me today that I was insane for relying on such a thing which is apparently well documented. So I began looking for another way to easily duplicate a transfer object.

Why I want to clone()

To take over the world, of course. Seriously, I have functionality in my application that allows you to clone an existing object changing just a few properties of it as a way to jumpstart data management. The easy way is to get the existing object and then do get/set on every property. But that kind of sucks. So I went about looking to write a generic object cloner.

First Attempt

Here was my first attempt, beginning with my Transfer definition:

<object name="event" table="events" decorator="event">
	<id name="EventID" type="UUID" generate="true" />
	<manytoone name="EventType">
		<link column="EventTypeID" to="event.eventtype" />
	</manytoone>
	<manytoone name="Track">
		<link column="TrackID" to="track.track" />
	</manytoone>
	<manytoone name="Club">
		<link column="ClubID" to="club.club" />
	</manytoone>
	<property name="RegistrarID" type="UUID" nullable="true" />
	<property name="NotifyOnEventReg" type="boolean" />
</object>

And here is the first pass of my Transfer decorator:

<cfcomponent displayname="event" output="false" extends="transfer.com.TransferDecorator">
	<cfproperty name="EventID" type="string" default="" />
	<cfproperty name="Club" type="string" default="" />
	<cfproperty name="Track" type="string" default="" />
	<cfproperty name="EventType" type="string" default="" />
	<cfproperty name="RegistrarID" type="string" default="" />
	<cfproperty name="NotifyOnEventReg" type="string" default="" />

	...
</cfcomponent>

And the clone routine in my model:

<cffunction name="clone" access="public" output="false" returntype="any">
	<cfargument name="object" type="any" required="true" />

	< !--- get properties as defined in the decorator that we can use to get/set --->
	<cfset var props = getMetaData(arguments.object).properties />
	< !--- get a new version of whatever we've passed in --->
	<cfset var obj = variables.transfer.new(arguments.object.getClassName()) />

	<cfset var prop = "" />
	<cfset var value = "" />
	<cfset var len = arrayLen(props) />

	< !--- copy values from prior obj --->
	<cfloop from="1" to="#len#" index="ii">
		<cfset prop = props[ii] />
		<cfinvoke component="#arguments.object#" method="get#prop.name#" returnvariable="value" />
		<cfinvoke component="#obj#" method="set#prop.name#">
			<cfinvokeargument name="#prop.name#" value="#value#" />
		</cfinvoke>
	</cfloop>

	<cfreturn obj />
</cffunction>

So Close, but So Far…

It seems to work at first, until you realize that any relationships (many2one, one2many, many2many) don’t work! While you normally do something like setRelationship(object), the CFINVOKEARGUMENT syntax passes a named parameter and Transfer expects an argument named “transfer”. So I went back to meddling with my metadata and came up with the following convention:

<cfcomponent displayname="event" output="false" extends="transfer.com.TransferDecorator">
	<cfproperty name="EventID" type="string" default="" />
	<cfproperty name="Club" type="any" default="" />
	<cfproperty name="Track" type="any" default="" />
	<cfproperty name="EventType" type="any" default="" />
	<cfproperty name="RegistrarID" type="string" default="" />
	<cfproperty name="NotifyOnEventReg" type="string" default="" />

	...
</cfcomponent>

And the corresponding generic cloner:

<cffunction name="clone" access="public" output="false" returntype="any" hint="takes any Transfer object and clones it">
	<cfargument name="object" type="any" required="true" />

	< !--- get properties as defined in the decorator that we can use to get/set --->
	<cfset var props = getMetaData(arguments.object).properties />
	< !--- get a new version of whatever we've passed in --->
	<cfset var obj = variables.transfer.new(arguments.object.getClassName()) />

	<cfset var prop = "" />
	<cfset var value = "" />
	<cfset var len = arrayLen(props) />

	< !--- copy values from prior obj --->
	<cfloop from="1" to="#len#" index="ii">
		<cfset prop = props[ii] />
		<cfset obj["set" & prop.name]>
		<cfinvoke component="#arguments.object#" method="get#prop.name#" returnvariable="value" />
		<cfinvoke component="#obj#" method="set#prop.name#">
			<cfif prop.type EQ "any">
				<cfinvokeargument name="transfer" value="#value#" />
			<cfelse>
				<cfinvokeargument name="#prop.name#" value="#value#" />
			</cfif>
		</cfinvoke>
	</cfloop>

	<cfreturn obj />
</cffunction>

Now we use the proper variable name of “transfer” for any CFPROPERTY with a type of “any”. By convention, my decorators will use “any” for my Transfer relationships and “string” for everything else. This seems to be working so far but I would appreciate input!

Comments are closed.