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

Posted August 6, 2009 at 6:40 PM by Ben Nadel

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

  • <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:

  • <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.




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 »
10,640 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 »
10,640 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 »
24 Comments

Very clever Ben!


Aug 17, 2009 at 2:11 PM // reply »
10,640 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 »
10,640 Comments

@DeepDown,

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


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »