Using A Proxy To Give Any Component Method-Chaining Abilities In ColdFusion

Posted August 6, 2009 at 6:40 PM

Tags: ColdFusion

When I was looking into using the CFQuery tag in CFScript using ColdFusion 9's new CFScript enhancement, it struck me that these ColdFusion service objects might be a good place to use method chaining. When working with these objects, we are building up values within a given CFC in order to call some sort of execution method on it. It seems that being able to chain the methods calls leading up that final execution would create a nice interface (ala jQuery).

At first, I started to think about extending the Query.cfc class, but then it occurred to me - using ColdFusion 8's OnMissingMethod() functionality (and some CF9 goodness), I can easily create a wrapper for any target CFC that will make the method calls on that target CFC chainable. Essentially, all the wrapper would have to do is proxy the method calls and return itself if the target method didn't return a value. To do this, I came up with the Chainable.cfc:

Chainable.cfc

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • output="false"
  • hint="I add chainability to my target component.">
  •  
  • <!--- Set properties. --->
  • <cfproperty
  • name="target"
  • type="any"
  • hint="I am the target component that is being made chainable."
  • />
  •  
  • <cfproperty
  • name="targetMethods"
  • type="string"
  • hint="I am the list of target methods that will be made chainable EVEN IF they return a result."
  • />
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return an initialized object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="target"
  • type="any"
  • required="true"
  • hint="I am the target component that is being made chainable."
  • />
  •  
  • <cfargument
  • name="targetMethods"
  • type="string"
  • required="false"
  • default=""
  • hint="I am the list of target methods that will be made chainable EVEN IF they return a result."
  • />
  •  
  • <!--- Store the arguments. --->
  • <cfset this.setTarget( arguments.target ) />
  • <cfset this.setTargetMethods( arguments.targetMethods ) />
  •  
  • <!--- Return this object instance. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I invoke the given method on the target object and chain the results (if possible).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the method that was invoked."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="struct"
  • required="true"
  • hint="I am the argument collection used to invoke the given method."
  • />
  •  
  • <!--- Invoke the given method on the target object. --->
  • <cfinvoke
  • component="#this.getTarget()#"
  • method="#arguments.missingMethodName#"
  • argumentcollection="#arguments.missingMethodArguments#"
  • returnvariable="local.result"
  • />
  •  
  • <!---
  • Check to see if we have a result. We want to chain
  • the given method if the result is NULL or if the
  • given method was in the list of target methods.
  • --->
  • <cfif (
  • isNull( local.result ) ||
  • listFindNoCase(
  • this.getTargetMethods(),
  • arguments.missingMethodName
  • )
  • )>
  •  
  • <!---
  • The results we got back from the target
  • invocation were either NULL or blacklisted.
  • Return THIS object for method chaining.
  • --->
  • <cfreturn this />
  •  
  • <cfelse>
  •  
  • <!---
  • The result we got back from the target invocation
  • was a valid result. Return it.
  • --->
  • <cfreturn local.result />
  •  
  • </cfif>
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the Chainable.cfc class doesn't really define any methods at all; it simply proxies calls to the target using OnMissingMethod(). Then, if the target method doesn't return a value, Chainable.cfc returns itself. As I was writing this though, it occurred to me that a lot of times a method will return a value that is not critical. For example, ArrayDelete() returns a boolean as to whether or not an array item was actually deleted. While this is good information to know, I built in the ability to make these sorts of methods chainable as well. The TargetMethods property of Chainable.cfc is a list of methods that will be chained even if they return a value.

With Chainable.cfc defined, I can now wrap my Query.cfc instances making their methods chainable:

 Launch code in new window » Download code as text file »

  • <cfscript>
  •  
  • // Create a new chainable query object. To do this,
  • // we have to create an instance of our Chainable
  • // component and passour new query instance to it.
  • getGirl = new Chainable( new Query() );
  •  
  • // Set the properties end execute. Notice that these
  • // methods calls are all being chained in one expression.
  • girls = getGirl
  • .setSQL("
  • SELECT
  • id,
  • name,
  • hair
  • FROM
  • girl
  • WHERE
  • name = :name
  • ")
  • .addParam(
  • name = "name",
  • value = "Tricia",
  • cfsqltype = "cf_sql_varchar"
  • )
  • .execute()
  • .getResult()
  • ;
  •  
  • // Output the resultant girl query.
  • writeOutput( "Girl: " & girls.name );
  •  
  • </cfscript>

As you can see, once the Query.cfc instance is wrapped in our Chainable.cfc instance, all native Query.cfc methods are now proxied through Chainable.cfc, making it completely chainable. When we run the above code, we get this output:

Girl: Tricia

Not everyone likes method chaining; and, in some cases, it can certainly make code difficult to read. But, I think that at times, it can really clean things up. In cases like this, for example, where several different method calls all relate to each other and are building up to a single execution, I think it is a really nice option.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Aug 6, 2009 at 7:21 PM // reply »
153 Comments

This is a very nice hack. Solid thinking, indeed.


Aug 6, 2009 at 7:32 PM // reply »
7,572 Comments

@Rick,

Thanks my man.


Aug 7, 2009 at 9:00 AM // reply »
5 Comments

Hey Ben,

Have you done any performance testing with this? Just curious...

Thanks!


Aug 7, 2009 at 9:22 AM // reply »
7,572 Comments

@Jared,

There will be some overhead since there is a proxy object and additional logic. However, from everything that I've been told, they built OnMissingMethod() functionality to be extremely fast because it was designed specifically to add logic around method calls.


Aug 7, 2009 at 9:23 AM // reply »
5 Comments

Awesome! Thanks!


Aug 14, 2009 at 12:29 PM // reply »
19 Comments

Very clever Ben!


Aug 17, 2009 at 2:11 PM // reply »
7,572 Comments

@Jared, @Hussein,

Thanks guys. I love chaining.


Aug 24, 2009 at 4:41 AM // reply »
2 Comments

nice article


Nov 20, 2009 at 8:52 AM // reply »
1 Comments

You just can't stop abusing the onMissingMethod feature, can you Ben? ;)


Nov 20, 2009 at 1:00 PM // reply »
7,572 Comments

@DeepDown,

I'll never stop.... NEVER :)


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 20, 2010 at 12:07 PM
Drawing On The iPhone Canvas With jQuery And ColdFusion
Simply awesome. Saved my day. ... read »
Mar 20, 2010 at 9:00 AM
Building A Fixed-Position Bottom Menu Bar (ala FaceBook)
I would like to say thx for an easy way to create a bottom bar. I do have a ?. Is it possible to center the bar if i want to resize it to ex 85%. Regards Offenbach ... read »
Mar 19, 2010 at 7:26 PM
MySQL 3/4 - com.mysql.jdbc.Driver And allowMultiQueries=true
Thank you very much for this post. Adding allowMultiQueries="true" in context.xml didn't help until I added it to url as allowMultiQueries=true Good idea is to use prepared statements and it will he ... read »
Jim
Mar 19, 2010 at 4:49 PM
Nobody Puts Baby In The Corner!
Wow. This is like suddenly finding a support group for your secret shame. I'm not alone! I always liked this movie, even though it is extremely cheesy. I just wish Jennifer Grey hadn't gotten the ... read »
Mar 19, 2010 at 4:47 PM
Application.cfc OnRequest() Method Affects OnError() Arguments
@Jason and @Ben, I've been doing some CF9 refactoring on our systems and noticed an odd occurrence with onError as well. Found a way to work around my problem, but what I saw was... Background: Our ... read »
Jim
Mar 19, 2010 at 4:44 PM
Shoot 'Em Up Starring Clive Owen And Paul Giamatti
I actually enjoyed this movie quite a lot. It was different, certainly, but I think they were going for more of a Quentin Tarentino-"wow, that was weird"-vibe than an actual spoof. Once I realize ... read »
Mar 19, 2010 at 4:34 PM
An Intensive Exploration Of jQuery With Ben Nadel (Video Presentation)
Hey I guess the video is down. Is there anyway you can upload to youtube or vimeo or some other service? Greatly appreciated. ... read »
Mar 19, 2010 at 4:24 PM
ColdFusion CFPOP - My First Look
@Ben Thanks for the follow up! The root of the problem had to do with being able to trace bounced emails to specific records in a DB table. Let's say you run an email campaign and you get 1,000 bou ... read »