Using Transfer Metadata to Create a Memento
Posted At : June 17, 2008 7:05 AM | Posted By : Bob Silverberg
Related Categories: ColdFusion, Transfer
This was asked today on the Transfer mailing list:
2
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:
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?" />
4
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.
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.
2 <cfset theProperty = Properties.next() />
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.
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.
For anyone interested in cutting and pasting, here's the whole function, with a few extra comments:
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?" />
4
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 />
14
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 = Properties.next() />
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>
43</cffunction>
Thanks for a good post!