Monday, February 21, 2011

ColdFusion ORM: Computed Property Gotcha

I was fighting an ORM issue today (well, several) and I came across an issue that is more of a gotcha than a real problem.  ColdFusion ORM supports the idea of computed properties.  I was using a computed property to give me the sum of all approved amounts in a child collection like so:

<cfcomponent displayname="Parent" persistent="true">
...
  <cfproperty name="totalFundingApproved" formula="select sum(ch.approved_amount) from child ch where ch.parent_id = id" />
...
</cfcomponent>

I'm using Ajax to persist records, and then I am returning the parent object to my callback function so I can then parse the object and update the UI with the new values.  The super-simplified service method would like like this:

var parent = entityLoadByPK('Parent', arguments.id);

child = parent.getChild();
child.setTotalFundingApproved(arguments.approvedAmount);
entitySave(parent);

return parent;

Now, it would be silly for ColdFusion to reload the values on the calculated property every time we ask for it.  The problem is, I need it to be updated when the child is updated!  There are a few things we could do to get the functionality we want.  First, we can move the computed property out into a method call:

<cffunction name="getTotalFundingApproved" access="public" returntype="query" output="false">
  <cfset var q = '' />
  
  <cfquery name="q" datasource="#dsn#">
  select sum(ch.approved_amount) from child ch where ch.parent_id = #getID()#
  </cfquery>
  
  <cfreturn q />
</cffunction>

The problem with that in my specific case is that I need to be able to sort on that property, and no matter how hard you try, you just can't sort on a method ;)  Another option is to create a method like getTotalFundingApproved().  This seems kludgy to me.  With proper documentation it could work, but you could have two possible values for the same property at any given time depending on the child collection's state.

I ended up reloading the object and returning that copy.  To ensure a fresh read I'm using the ORMClearSession() method:

ormclearsession();
var parent = entityLoadByPK('Parent', id);
return parent;

This works, but I'd love to see a way to trigger a reload of the computed property that I could use in the preUpdate() or preInsert() event handlers.

Update (2/22/2011): I was looking through the new functions in CF 9 today and found entityReload(). This could be used instead of ormClearSession(). ormClearSession() worked for me here since no other objects were in the session at the time. In other cases, entityReload() just makes more sense.

2 comments:

  1. Great post Nic!

    I ran into a problem not long ago with entityReload() that might be worth mentioning.

    I was having lots of trouble updating an obj in an (application, not ORM) session using entityReload(). My unit tests for the model were a nice shade of green, but wrestling with the implementation was a pain!

    Unfortunately, I can't recall the exact problem/soln but tinkering with the cascade settings with using entityReload() did the trick.

    From the online CF9 documentation:

    Developing ColdFusion 9 Apps / ColdFusion ORM / Define ORM mapping / Define Relationships - Cascade Options:

    cascade="refresh" : Cascades the refresh action to the child object. The refresh action is used to reload an object and its collections

    my $.02

    - Ray V

    ReplyDelete
  2. Thanks for this post--helped me out a bunch!

    ReplyDelete