Photo from Chile

Using Transfer Metadata to Create a Memento

This was asked today on the Transfer mailing list:

view plain print about
1Looking through the documentation I couldn't see any generated methods which will return all the objects properties as a structure of 'propertyname:value'.
3Does a method get generated which handles this? If not, what do you see as the best way to build one?

I thought that my response was worthy of a blog post, so here goes.

Firstly, there is a getMemento() method that gets generated for all Transfer objects, which returns this type of struct. The problem is that it is undocumented and therefore unsupported. This method could change and/or disappear at any time, so it's not really safe to use. I use it occasionally while developing and debugging, but I'd never use it in real application code.

Transfer does provide access to its own internal metadata via getTransferMetaData(), which you can call on the base Transfer class. This metadata is chock full of useful information which I've made use of in a number of situations. I too needed to generate a simple struct that contained property name/value pairs, so I wrote a method which I've placed in my AbstractTransferDecorator which serves that purpose. Here's the code, broken up into sections to allow for some explanation:

view plain print about
1<cffunction name="copyToStruct" access="public" output="false" returntype="void" hint="Copies data from the TO into a Struct">
2    <cfargument name="theStruct" type="any" required="false" default="#StructNew()#" hint="A struct to receive the data" />
3    <cfargument name="Overwrite" type="any" required="false" default="true" hint="Should existing values in the struct be overwritten?" />
5    <cfset var TransferMetadata = getTransfer().getTransferMetaData(getClassName()) />
6    <cfset var Properties = TransferMetadata.getPropertyIterator() />
7    <cfset var PrimaryKey = TransferMetadata.getPrimaryKey() />
8    <cfset var theProperty = 0 />
9    <cfset var varName = 0 />
10    <cfset var varType = 0 />
11    <cfset var varValue = 0 />
12    <cfset var i = 0 />

So first I grab the Transfer metadata for this class. getClassName() is a generated method that exists for every Transfer object. I know I want to populate keys in my structure for all of the properties of my Transfer object, so I grab the PropertyIterator from the metadata. My primary key, identified as an id in transfer.xml will not be included in the PropertyIterator, so I need to ask for that separately.

view plain print about
1<cfset varName = PrimaryKey.getName() />
2    <cfinvoke component="#this#" method="get#varName#" returnvariable="varValue" />
3    <cftry>
4        <cfset StructInsert(arguments.theStruct,varName,varValue,arguments.Overwrite) />
5        <cfcatch type="any"></cfcatch>
6    </cftry>

I use the PrimaryKey from the metadata to determine the name of id, which I then use to call the associated get() method. I then add this name/value pair to the struct. Because I want this routine to be able to add information to an existing struct, I may not always want it to overwrite any existing keys in the struct, which is the reason that I'm allowing the caller to pass in an argument for overwrite. The StructInsert function allows you to specify overwrite as either true or false, so this allows me to implement this logic. Unfortunately, if you specify overwrite as false and the key actually exists an exception is thrown, which is the reason that I've wrapped my call to StructInsert in a cftry. There's probably a more elegant way of doing this, but I haven't taken the time to find one... yet.

view plain print about
1<cfloop condition="#Properties.hasnext()#">
2        <cfset theProperty = />
3        <cfset varName = theProperty.getName() />
4        <cfinvoke component="#this#" method="get#varName#" returnvariable="varValue" />
5        <cftry>
6            <cfset StructInsert(arguments.theStruct,varName,varValue,arguments.Overwrite) />
7            <cfcatch type="any"></cfcatch>
8        </cftry>
9    </cfloop>

I loop through the PropertyIterator, doing the same thing as above - getting the value with a getter and then adding it to the struct.

At this point I have my id and all properties copied into my struct. An issue I encoutered is that I have some custom methods in my decorator and they are not exposed as properties to Transfer. For example, you might define a getAge() method which calculates a person's age based on their date of birth and the current date. if you want that copied to the struct as well, it won't happen if you rely solely on Transfer metadata. To allow for this I define a variable in the Configure() method of my object's decorator called ExtraProperties, and I populate it with an array of property names. These names correspond with the custom getters that I created, and for which I want values copied into my struct.

view plain print about
1<cfif IsDefined("variables.myInstance.ExtraProperties") AND IsArray(variables.myInstance.ExtraProperties) AND ArrayLen(variables.myInstance.ExtraProperties)>
2        <cfloop from="1" to="#ArrayLen(variables.myInstance.ExtraProperties)#" index="i">
3            <cfinvoke component="#this#" method="get#variables.myInstance.ExtraProperties[i]#" returnvariable="varValue" />
4            <cftry>
5                <cfset StructInsert(arguments.theStruct,variables.myInstance.ExtraProperties[i],varValue,arguments.Overwrite) />
6                <cfcatch type="any"></cfcatch>
7            </cftry>
8        </cfloop>
9    </cfif>

And that's the end of the method.

view plain print about

For anyone interested in cutting and pasting, here's the whole function, with a few extra comments:

view plain print about
1<cffunction name="copyToStruct" access="public" output="false" returntype="void" hint="Copies data from the TO into a Struct">
2    <cfargument name="theStruct" type="any" required="false" default="#StructNew()#" hint="A struct to receive the data" />
3    <cfargument name="Overwrite" type="any" required="false" default="true" hint="Should existing values in the struct be overwritten?" />
5    <!--- Get the MetaData and Properties --->
6    <cfset var TransferMetadata = getTransfer().getTransferMetaData(getClassName()) />
7    <cfset var Properties = TransferMetadata.getPropertyIterator() />
8    <cfset var PrimaryKey = TransferMetadata.getPrimaryKey() />
9    <cfset var theProperty = 0 />
10    <cfset var varName = 0 />
11    <cfset var varType = 0 />
12    <cfset var varValue = 0 />
13    <cfset var i = 0 />
15    <!--- Put the Id into the Struct --->
16    <cfset varName = PrimaryKey.getName() />
17    <cfinvoke component="#this#" method="get#varName#" returnvariable="varValue" />
18    <!--- Need cftry because if overwrite is false an error can be thrown --->
19    <cftry>
20        <cfset StructInsert(arguments.theStruct,varName,varValue,arguments.Overwrite) />
21        <cfcatch type="any"></cfcatch>
22    </cftry>
23    <!--- Put the properties into the theStruct --->
24    <cfloop condition="#Properties.hasnext()#">
25        <cfset theProperty = />
26        <cfset varName = theProperty.getName() />
27        <cfinvoke component="#this#" method="get#varName#" returnvariable="varValue" />
28        <cftry>
29            <cfset StructInsert(arguments.theStruct,varName,varValue,arguments.Overwrite) />
30            <cfcatch type="any"></cfcatch>
31        </cftry>
32    </cfloop>
33    <!--- Add the extra properties into the Struct --->
34    <cfif IsDefined("variables.myInstance.ExtraProperties") AND IsArray(variables.myInstance.ExtraProperties) AND ArrayLen(variables.myInstance.ExtraProperties)>
35        <cfloop from="1" to="#ArrayLen(variables.myInstance.ExtraProperties)#" index="i">
36            <cfinvoke component="#this#" method="get#variables.myInstance.ExtraProperties[i]#" returnvariable="varValue" />
37            <cftry>
38                <cfset StructInsert(arguments.theStruct,variables.myInstance.ExtraProperties[i],varValue,arguments.Overwrite) />
39                <cfcatch type="any"></cfcatch>
40            </cftry>
41        </cfloop>
42    </cfif>

Related Blog Entries

Bob, I'm loving your blog. Really good stuff. Thanks mucho for it. Making my Transfer development look mighty mighty slick.
# Posted By John Allen | 6/18/08 11:19 AM
Thanks John. Your kind words are greatly appreciated, and I'm glad that you are finding some of my posts useful.
# Posted By Bob Silverberg | 6/18/08 11:44 AM
Bob, this is awesome. Exactly what I needed. Works great. I've added it to my abstract decorator and I look forward to using it regularly.
# Posted By Jason Dean | 9/2/08 8:34 PM
Thanks Jason. I'm glad you found it useful.
# Posted By Bob Silverberg | 9/3/08 2:30 PM
Jason recommended I try you method instead of the getMemento and it works great! especially in the abstract decorator method!

Thanks for a good post!
# Posted By Paul Ling | 1/17/09 3:07 PM
you da man Bob! how's it going? thanks for this. allowed me to do what i needed when returning a transfer object via cfajaxproxy. now I can populate the DOM!
# Posted By Bim | 8/21/09 8:02 PM