More on CFLib update (Transfer specifics)

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.

Comments

Sorry i do not understand the
<cffunction name="getLibrary" access="public" returnType="any" output="false">
why not "publics" why public
# Posted By Photovoltaik | 6/10/08 9:30 AM
No attachment Raymond ;-)
# Posted By dickbob | 6/10/08 9:41 AM
@dickbob: Sorry, added.

@photovoltaik: Eh? Are you asking why I used public instead of publics? Publics isn't valid.
# Posted By Raymond Camden | 6/10/08 9:48 AM
Stupid question of the day... what is "Transfer?" It isn't exactly a google friendly product name.
# Posted By Brian | 6/10/08 10:46 AM
Ray, glad to hear you drank the ORM kool-aid, and look forward to your blogging about it.

Welcome to this wonderful wonderful world.

Thank goodness for the Mighty Mighty Transfer ORM.
# Posted By John Allen | 6/10/08 11:09 AM
@Brian, check out http://www.transfer-orm.com . I agree, not the friendliest name to search for, but there you go! I agree with Ray, Transfer is really quite useful. I switched from Reactor to Transfer, and love the change.
# Posted By Cedric Villat | 6/10/08 11:11 AM
@Brian - Cedric answered this well. It is an ORM. ORM means Object Relational Mapper.

In English - consider it an API to your datasource. It can help you write no, or little, SQL.

So I can say "Table so and so has these fields." And then my orm lets me do "Get record 5" and the orm handles writing the proper sql.
# Posted By Raymond Camden | 6/10/08 11:29 AM
it's a bit of a pain typing out the transfer.xml, is it possible to generate it from db introspection, perhaps using cfdbinfo?
# Posted By me | 6/12/08 4:42 PM
Sure. And I believe Brian Rinaldi's generator can do this as well.
# Posted By Raymond Camden | 6/12/08 4:44 PM
Thanks Ray, Brian's cfcgenerator is really helpful. Just worth noting I think though, for those starting out with transfer like me, make sure you delete the foreign key properties from your transfer.xml when setting up your links/relationships, the generator puts them in even if they're defined as fks in your db. Had me scratching my head for a while.
# Posted By me | 6/14/08 3:54 PM