Yesterday in my blog entry on the CFLib relaunch I mentioned that I'd talk a bit more about my experience with Transfer. What follows is my experience, and my mistakes, and also what I learned (with a lot of help from Mark Mandel), but please keep in mind that I'm still quite new to this.

My biggest mistake was in how I organized my Transfer objects. I began by defining a library and udf object.

<objectDefinitions>

<package name="library">

<object name="library" table="tblLibraries"> <id name="id" type="numeric"/> <property name="name" type="string" /> <property name="shortdescription" type="string" /> <property name="description" type="string" /> <property name="lastupdated" type="date" /> <property name="owner" type="string" /> <property name="owneremail" type="string" /> <property name="released" type="string" /> <onetomany name="udfs"> <link to="udf.udf" column="libraryidfk"/> <collection type="array"> <order property="name" order="asc"/> </collection> </onetomany> </object>

</package>

<package name="udf">

<object name="udf" table="tblUDFs"> <id name="id" type="numeric"/> <property name="name" type="string" /> <property name="shortdescription" type="string" /> <property name="description" type="string" /> <property name="returnvalue" type="string" /> <property name="example" type="string" /> <property name="warnings" type="string" /> <property name="code" type="string" /> <property name="args" type="string" /> <property name="released" type="boolean" /> <property name="lastupdated" type="date" /> <property name="author" type="string" /> <property name="authoremail" type="string" /> <property name="javadoc" type="string" /> <property name="version" type="numeric" /> <property name="headercomments" type="string" /> <property name="exampleother" type="string" /> <property name="rejected" type="boolean" /> <property name="rejectionreason" type="string" /> <property name="cfversion" type="string" /> <property name="tagbased" type="string" /> <property name="ratecount" type="numeric" /> <property name="ratetotal" type="numeric" /> </object>

</package>

</objectDefinitions>

Even if you don't know Transfer, this should make sense. Basically I've created an XML file to reflect my table definitions. But pay special attention to the library block, specifically this area:

<onetomany name="udfs"> <link to="udf.udf" column="libraryidfk"/> <collection type="array"> <order property="name" order="asc"/> </collection> </onetomany>

If there is such thing as "hot" xml, this is it. This one block allows me to easily get a Library, and then easily get all the UDFs associated with the library. This works great. Until I tried this with StrLib and it's 319 UDFs. As you can imagine, even with ColdFusion 8 dramatically improving CFC creation, this operation was extremely slow.

Transfer does let you specify "lazy=true" in the onetomany block. This will only load the related objects when you ask for them, but I needed the UDFs every time the library was viewed.

Mark made the obvious recommendation - switch to a query. I updated my model to support a new method that would get a set of UDFs based on the library, a starting index, and a max number. This then let me easily handle my pages of UDFs. I felt a bit bad because I thought my model was a bit too closely concerned with the view, but then I had a Coke and got over it. Here is how the controller method looks:

<cffunction name="getLibrary" output="false"> <cfargument name="event" /> <cfset var libid = arguments.event.getValue("libraryid")> <cfset var library = beans.libraryService.getLibrary(libid)> <cfset var perpage = beans.config.getConfigSetting("perpage")> <cfset var udfs = ""> <cfset var start = arguments.event.getValue("start")>

<cfif not isNumeric(libid) or libid lte 0 or round(libid) neq libid or library.getID() is 0> <cfset arguments.event.addResult("BadLibrary")> </cfif>

<cfset arguments.event.setValue("library", library) />

<cfif not isNumeric(start) or start lte 0 or round(start) neq start> <cfset start = 1> </cfif>

<!--- we only load a set of UDFs at a time, based on a page ---> <cfset udfs = beans.UDFService.getUDFsForLibrary(libid,start,perpage)> <cfset arguments.event.setValue("udfs",udfs)> </cffunction>

This change created another problem. How do I report the number of UDFs for an individual library. The home page for CFLib is using a simple query. But on the individual pages I created a Transfer decorator. Transfer automatically creates bean objects for your data. That rocks. These beans are based on the XML declaration for your data. But sometimes you need to extend the bean a bit. For my library I needed to add a get/setUDFCount. By using a decorator I tell Transfer, "Make your normal bean for this object, but I've extended it a bit in this CFC here..."

I changed my XML for the library object to this:

<object name="library" table="tblLibraries" decorator="cflib2008.model.library">

And then made the CFC:

<cfcomponent extends="transfer.com.TransferDecorator">

<cffunction name="setUDFCount" access="public" returnType="void" output="false"> <cfargument name="count" type="numeric" required="true"> <cfset variables.count = arguments.count> </cffunction>

<cffunction name="getUDFCount" access="public" returnType="numeric" output="false"> <cfif structKeyExists(variables,"count")> <cfreturn variables.count> <cfelse> <cfreturn 0> </cfif> </cffunction>

</cfcomponent>

So nothing too complex here - just a basic get/set. My libraryGateway though handles doing the work for me:

<cffunction name="getLibrary" access="public" returnType="any" output="false"> <cfargument name="id" type="any" required="true"> <cfset var library = ""> <cfset var count = "">

<cfif structKeyExists(arguments, "id") and arguments.id neq ""> <cfset library = variables.transfer.get("library.library", arguments.id)> <cfelse> <cfset library = variables.transfer.new("library.library")> </cfif>

<cfquery name="count" datasource="#variables.dsn#"> select count(id) as total from tbludfs where libraryidfk = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.id#"> and released = 1 </cfquery>

<cfset library.setUDFCount(count.total)>

<cfreturn library> </cffunction>

As you can see, up top I handle getting the library. (Again, I love how short and sweet that is.) I then do my custom sql to get the count and set the value in the bean.

In case folks want to see more of the code, I've zipped up the entire site and have attached it to this blog entry. Please note though that this is the first time I used Transfer from scratch (we use it a lot at Broadchoice) and the first time I wrote a site in Model-Glue 3. Therefore you should not consider this best practice. (Unless it is - and then I rock.) I didn't include any db scripts either. This is just for folks who want to look at the code behind the site.

Download attached file.