ColdFusion 10 - Don't Use Dynamic Query Values In CFLoop
As of ColdFusion 10, the "Query" attribute of the CFLoop tag can now be dynamic. That is, it can refer to a dynamic expression rather than a static name (string). At first, this seems like a really cool idea - one that you've probably even wanted over the years; but, don't do it. Using a dynamic query value forces you to, as far as I can tell, use unscoped query column values. Granted, this is just my personal opinion but, I feel very strongly that this is a bad idea.
NOTE: At the time of this writing, ColdFusion 10 was in public beta.
Before ColdFusion 10, if you wanted to use a dynamic query value in the CFLoop tag, you'd have to store the query reference in an intermediary variable before the CFLoop tag was invoked. Then, the name of the intermediary variable would have to be used in the Query attribute of the CFLoop tag. With ColdFusion 10, this is no longer a requirement - now, you can put your dynamic query expression directly in the CFLoop tag attributes:
<cfoutput> <!--- Build up a query. ---> <cfset friends = queryNew( "id, name", "cf_sql_integer, cf_sql_varchar", [ [ 1, "Tricia" ], [ 2, "Joanna" ], [ 3, "Sarah" ] ] ) /> <!--- This is just a helper function. ---> <cffunction name="getQuery"> <cfreturn friends /> </cffunction> <!--- Loop over the query returned from the getQuery() function. Notice that the query value no longer has to be static. ---> <cfloop query="#getQuery()#"> [ #id# ] #name#<br /> </cfloop> </cfoutput>
Notice that the query attribute of my CFLoop tag is using the expression, getQuery(). This gets the query from the given function and pushes it onto the query scope of the CFLoop tag. When we run the above code, we get the following page output:
[ 1 ] Tricia
[ 2 ] Joanna
[ 3 ] Sarah
This seems really awesome; but, if you look at the column references within the CFLoop tag body, you'll notice that they are all unscoped. This is because our dynamic query value is not attached to any variable name that we can access (as far as I can tell). This creates ambiguous references and prevents us from being able to augment the query object.
Trust me, this will come back to bite you. Your query column references should always be explicitly scoped.
As a fun experiment, I wanted to see if I could use the setVariable() function as a way to combine the dynamic query expression with the creation of an intermediary variable that would afford column scoping. As I mentioned six years ago, the setVariable() function is one of the ways that ColdFusion allows dynamic variable assignment. In most cases, setVariable() is not the right choice, but in this case, it's one of the few things that will compile.
NOTE: Using evaluate() in the query value will also compile and function properly.
<cfoutput> <!--- Build up a query. ---> <cfset friends = queryNew( "id, name", "cf_sql_integer, cf_sql_varchar", [ [ 1, "Tricia" ], [ 2, "Joanna" ], [ 3, "Sarah" ] ] ) /> <!--- This is just a helper function. ---> <cffunction name="getQuery"> <cfreturn friends /> </cffunction> <!--- Loop over the query returned from the getQuery() function. Notice that we're not just passing-in the dynamic query value; rather, we're assigning it to an intermediary variable, peope, which is then passed onto the CFLoop tag. ---> <cfloop query="#setVariable( 'people', getQuery() )#"> [ P #people.id# ] #people.name#<br /> </cfloop> </cfoutput>
In this demo, we're getting the dynamic query value from getQuery(), assigning it to the intermediary variable, "people," and then passing it onto the CFLoop tag. By doing this, it allows us to scope the query column values with the prefix, "people". This allows us to implement the best-practices approach to referencing query columns.
When we run the above code, we get the following page output:
[ P 1 ] Tricia
[ P 2 ] Joanna
[ P 3 ] Sarah
As you can see, this works, dynamica query assignment and all.
I'm not necessarily advocating this latter approach; I'm not necessarily saying it's bad, either. The only point of this post is to caution heavily against using any approach that requires query column values to be referenced without explicit scoping.
End rant :)
Want to use code from this post? Check out the license.
Great catch, and excellent advice. I find over and over again, when reviewing older (or junior developer) code, that the shortcuts taken to leave off the query variable references within cfoutput and cfloop inevitably lead to variable clashing and unexpected results.
Always always always scope those variables people :). Ben says so!
From a more practical standpoint, doing this makes adding other standards elements difficult or impossible, like showing "XXX records found" above the list, or adding the code needed to support pagination.
If your that worried about scoping your code is not modular enough! ;) (I realize on older code bases this might not be a possible change).
And is it that big of a deal considering that query scope goes above everything else?
You still have access to the helper variables, currentRow, etc, as shown below:
<cfquery name="getArt" datasource="cfartgallery" maxrows="10">
<cffunction name="getQuery" >
<cfset artName = "Get Art">
Unscoped query columns is one of those things that is totally fine when you're writing the code; and then a horrible choice when you have to maintain that code. In trivial examples, the values seem obvious; but, once you start dealing with non-trival View rendering, "magical" variables can become an enormous headache (in my experience).
I've relaxed a lot of the years when it comes to scoping values since most variable reference relate to an explicit variable declaration (including attributes, arguments, etc.). But, when it comes to query columns, if you hit a variable reference and think, "Where did this come from?", and then you scroll up trying to find a variable declaration, you'll not find one. This is definitely confusing in the long-term.
In the end, this is just a personal opinion.
"In the end, this is just a personal opinion."
It just so happens that you have the right opinion this time :-P
SOOOOOO many issues have been resolved by using <QueryName>.<ColumnName> in code I have debugged over the years... Especially with something as simple as a column named "ID" where there is an application-level definition for the "ID" variable.
"Trust me, this will come back to bite you. Your query column references should always be explicitly scoped."
I stopped reading there. Yikes Ben, just yikes. Huge fan of your blog but this post seems misguided in my opinion.
Using contextually implicit query column referencing in ColdFusion is a core, powerful, beautiful, extremely readable and natural feeling authoring strategy, one of a small handful of flagship examples of "the ColdFusion methodology".
Now I'm a huge fan of explicitly scoping ALL references except variables and these, even arguments. But explicitly scoping in-query column references ( with a few exceptions, like nested query loops ) is about as productive as explicitly scoping variables-scope references: it's verbose, convoluting, and diluting of the code's conciseness and purity ( a core tenet of ColdFusion ) and in the eclipsing majority of cases: pointless.
If your'e within a query loop you know what's happening, the guesswork and room for confusion that you and a few others have alluded to is in your heads, I'm sorry. The debugging scenarios for naming collisions are very simple and obvious, I'd be semi-astonished to see someone actually flounder with "variable clashing and unexpected results".
If your query loops are extremely complex and obscuring it may be time to revisit how much business logic and data manipulation you have in your view. Using a dynamic expression as the engine for a query loop is IMO clearly intended to be used for interface generation use cases.
<cfloop query="#application.articles.get( max = 10 )#">
So nice. For me this is one of a large handful of KILLER FEATURES IN CF 10!!!! I'm sorry you had to find out like this haha.
Seeing the back and forth arguments here, I'd comment that whether or not scoping query variables is a wise choice depends on context. Simple examples are shown here that may not reflect the complex code that some of us deal with (not necessarily in a view!!). I also understand that scoped variables run faster, because the CF engine doesn't have to look up each scope to determine which it belongs to. Just because an unscoped variable is contained within a query loop does not imply that it has to belong to the query. Scoping all variables helps keep my mind from completely losing track of what's going on in the complex chains of logic and code processing that I'm working with. Not scoping query variables in these cases would make the code MUCH more difficult to work with, especially when I come back to it a day, a week, or a month later!
@Sam - If you're not worried about scoping your code, I envy the simplicity of the apps you build and maintain!
@Sam If you're not that worried about scoping, you must not have very heavy loads in large applications. A little extra typing up front saves billions/trillions of CPU cycles down the road, not to mention preventing scoping issues in large complex apps.
The downside of coldfusion's ease of use and rapid dev cycle is the plethora of sloppy coders that skate by because the engine ties your shoes for you. Right up until the hit a brick wall with a complex codebase.
The stories I could tell about my "favorite" walking scope disaster, Andres...
Many ways to skin a cat.
@Sam sure, many ways, but there is always a proper way and a shortcut way that leaves a mess for someone to clean up.
I prefer the proper way that leaves zero room for confusion on the part of other coders or the compiler, and increases performance.
As @Sam says, there's many ways to skin a cat. I am always trying to find the way that works best for me.
In that previous post (on coding methodology), I've talked about less scoping for things like Arguments and Local scope where there is an explicit definition of the variables. I don't want to look at this as a pros/cons of scope vs. unscope; rather, I like to think of it as intuitive vs. unintuitive - explicit vs. implied. Everyone is going to have their own definition here. And, for me, unscoped query columns just feel very unintuitive. And, when I'm looking at someone *else* code, VERY unintuitive.
It's all about what you're used to seeing and what you expect.
Can cfcase value attributes be dynamic in CF 10 too? Really tedious maintaining a long string cfcase value that you can't even break up with ampersands.
I didn't see it in the CF 10 beta PDF, but then, I didn't notice dynamic query attributes either.
I'm just wondering, is it possible to get the cfloop execution context?
And if so, wouldn't it be possible to access the query over which you loop to then reference it when accessing its values?
Ben, I like this discussion a lot, particularly in terms of adding additional items like count to a view around the query, etc.
Usually, the only time I've personally seen scoping issues with queries come up was when looping over a query inside a loop of another query, where both are often likely to have a title column, etc. That being said, I don't always scope my query variables when outputting because that is a very small set of cases since I usually try to avoid nested loops in general and will save content a loop over a query if I'm going to be outputting the result of that same loop over and over.
I am also a proponent of scoping everything except context local variables and query vars as described. I believe the performance on the scope look up so negligible that the readability and simplicity of it can easily overwhelm the need to scope in these cases. That may also have to do with other decisions we make in our development styles as well though. I traditionally have very few variables in the Variables scope and absolute minimal object creation per request of any data structure. In addition, my team makes real use and separation of the Request scope and the Variables scope. If a variable is intended to be around for the whole request it goes into the Request scope, where each reference is then scoped as well. In the end, I'm looking at a very very small set of possible references to look through even on pretty complex apps.
Another thing this makes reminds me of is something that I've been thinking of for a while, which is the idea that cfloop could be more generic. CF is in a great position to (in future versions of course) make looping or cfoutput as simple as something like:
<cfloop over="query|array|struct|any collection" item="itemVariable" index="indexVariable">
This would allow the item and index attributes to be optionally used, whenever you need them or both of them, and both could easily be based on context. Item would always be the current row of the query, or the current item of an array, or the current value of the key in the structure. Index would always be the counter or key used to access that value. Hell for structures you could even add a counter attribute if we wanted to go really crazy that would simply offer you a counter for structure and fancier loops. In 99% of cases this would remove any view level changes based on using a query, array / hibernate result, etc.
I digress :-P
Once again, you always start a great and thorough conversation Ben.
The problem with that kind of feature is that the for-in loop is used to map to the fast underlying java for-in construct which does not have those features. In fact, I cant think of any of the big languages with a for-in having those features
That's irrelevant. Adobe already has their Java that translates your CFML into the loop processing, they simply have to do what we do now in CF ( unit and increment a numerical counter manually ) in their Java. I've requested this feature twice now and I know countless others have too. It would make working with in loops much nicer,
Stupid autocorrect, init. Not unit.
Holly hell. Numeric. Ok that's it, a "retina" display is not worth coming off as ESL in your typing.
I've already filed #3041787 for <cfloop name=""
Among other things, it would permit a loop's dynamic queries to be scoped.
I've just added this comment to the ticket:
For dynamic query loops, the 1st line of my example could be:
<cfloop query="#getData()#" name="outerLoop" group="myColumn1">
This would permit the scoping of a loop's dynamic queries.
Example: If getData() returns a query containing a "foo" column, then this would permit scoping of that query column as: "outerLoop.foo".