Brian asks:
Your rantings about var scoping CFC function variables has really made me look closely to how I code. If I var scope the name of a query before querying a datasource, will that query object become local to the function as well?
Rant? Me? Ok - so maybe I go a bit off the deep end at times with var scoping. The main reason is that it makes stuff very hard to debug if something goes wrong. But to answer your question, yes, if you var scope the name of the query before you use it, it will keep the query local to the function. This is how I do it normally:
<cffunction name="foo" returnType='query" output="false" access="public">
<cfset var q = "">
<cfquery name="q" datasource="kingsweallare">
select name from goo
</cfquery>
<cfreturn q>
</cffunction>
Notice that I did not define q as a query. Since CF is typeless, this is perfectly fine.
Archived Comments
To keep from hardcoding the Datasource, I've learned to do an Init first.
<cfcomponent displayname="xxx" output="False">
<cffunction name="Init" output="true" returntype="components.xxx">
<cfargument name="DSN" required="yes">
<cfset Variables.DSN = arguments.DSN>
<cfreturn this>
</cffunction>
</cfcomponent>
In my calling cfm page:
<cfset xxxObj = CreateObject("Component","Components.xxx").Init(Application.DSN)>
<cfset xxxQry = xxxObj.foo()>
In Application.cfc:
<cffunction name="onApplicationStart" output="false" returntype="void">
<cfset Application.DSN = "myDatasource">
</cffunction>
Nod - I was going for quick and simple though. ;) Plus this question applies to UDFs as well, so I wanted to keep it generic.
I also include a Diagnostic function with every component file.
<cffunction name="Diagnostic" access="public" returntype="string" output="True">
<cfset var result = "OK">
<cfset var xxxObj = CreateObject("component","Components.xxx").Init(Variables.DSN)>
<cfset var xxxQry = ''>
<cfset var xxxID = ''>
Foo
<cfset xxxQry = xxxObj.Foo()>
<cfif xxxQry.recordcount EQ 0>
<cfset result="FAILED">
<cfreturn result>
</cfif>
<cfreturn result>
</cffunction>
This allows me to write a Diagnostic.cfm file that has:
<cfset Obj = CreateObject("component","components.xxx").Init(Application.DSN)>
#Obj.Diagnostic()#
The Diagnostic function in xxx.cfc should call every function within xxx.cfc and return OK if they all work.
This allows you go proceed with confidence as you make changes.
When something breaks, it's reassuring to browse Diagnostic.cfm and see that all the components are working.
Now that is interesting Phillip. Normally I'd suggest folks use a "full" unit testing system - but this seems like a nice and simple solution. Do you have a blog? I think you do. You should blog this.
Given how important it is to var scope variables, and that many newbies forget to do it on a large scale (and even experienced devs miss one here and there), has anyone thought of writing a code analyzer to check for non-var scoped variables?
As long as all non-local variables are fully scoped to the "this" or "variables" scope then it shouldn't be too hard to write a few regular expressions to identify local variables and then check to see if they were var scoped at the top of the function.
I envision adding a unit test to CFUnit that examines the CFC code and throws warnings if non-var scoped variables are found. This way the checks are run each time a CFC is re-tested to make sure recent additions didn't introduce new, non-var scoped locals.
If nothing like this exists I think I may have a go at it. Maybe its just the influence of reading Ray's blog every day, but I've been itching to release some sort of open source app to the community :)
I say go for it.
I use a shortcut like this:
<cffunction .....>
<cfset var l = structNew() />
<cfset l.someVariable = "locally scoped" />
<cfquery name="l.qry_myquery" ...>
BLAH...
</cfquery>
</cffunction>
I always scope all my variable references inside functions with arguments.xxx / variables.xxx / this.xxx / l.xxx
That way I can easily tell just by looking at the code if anything is not properly scoped.
The only exception to this otherwise-nice-way-of-doing-it is if you then need to do a Query-of-Queries with a fully-scoped reference to your locally-scoped query - as you need to do if you want to do joins in a Q-of-Q
<cfquery name="l.qry_query2" ...>
SELECT
l.qry_one.itemID,
l.qry_two.whatever
FROM
l.qry_one, l.qry_two
WHERE
l.qry_one.itemID = l.qry_two.itemID
</cfquery>
If you don't fully-scope the reference, then it fails on the Ambiguous Column Name.
If you do fully-scope the reference, then it fails because it can't resolve it due to the dot!
So in that case, I explicitly declare it as var at the top, and prefix the variable name with LOCAL_varname.
Seems to work OK for me :)
Ray,
Simple request. When an individual enters their website, if they don't include the 'http' or 'https' the links don't work. Any chance of adding form validation or simply a note that says "Include http(s)://"?
It's on my list. Will be in the next update. (After I dig up a proper license. -sigh-)
Alistair: I guess I should get into that habit as well, especially for larger methods, but I don't necessarily like the idea of creating a struct on every single method call.
However, I realize that the performance hit of creating that struct is very small, especially compared to the time needed for a non-trivial method to do its work, so I really can't defend my position logically. Maybe I'm just lazy and stuck in my ways :)
Seth,
I just like the idea, as often mooted by Joel Spolsky, of having your coding conventions setup in such a way as to make potential bugs leap out of the page at you. This was I believe the original motivation behind the Hungarian Notation that we all know and love (ahem!)
Besides, I set up a snippet for it, so I can put the relevant script block into my functions just by typing "L,(CTRL+J)".
Al
The reason why it's such a simple solution is because I'm still trying to learn the syntax of CF.
Don't know nothing 'bout no XML.
Don't know nothing about H.T.M.L.
But I do know 1 and 1 is 2.
Somebody stop me!
Don't sell yourself short Phillip, it's a step above where I am. And I've been doing CF for 3 years. Slow learner i guess :)
Move over Ray, I'm taking over this thread ;-)
OK, the next thing you have to do is add error trapping.
xxx.cfc contains:
<cfcomponent displayname="xxx" output="False">
<cffunction name="Init" output="False" returntype="components.xxx">
<cfargument name="DSN" required="yes">
<cfset Variables.DSN = arguments.DSN>
<cfreturn this>
</cffunction>
<cffunction name="Foo" output="False" returntype="query" hint="I return everything from xxxView">
<cfargument name="xxxID" type="numeric" required="no">
<cfset var Condition = "">
<cfset var rst = "">
<cftry>
<cfquery name="rst" datasource="#Variables.DSN#">
SELECT * FROM xxxView
<cfif isDefined("arguments.xxxID") and isValid("integer",arguments.xxxID)>
WHERE xxxID = #arguments.xxxID#
</cfif>
</cfquery>
<cfcatch type="database">
<cfset rst = QueryNew('Error,Type','Varchar,Varchar')>
<cfset QueryAddRow(rst,1)>
<cfset QuerySetcell(rst,'Error',cfcatch.Detail)>
<cfset QuerySetcell(rst,'Type',cfcatch.Type)>
<cfreturn rst>
</cfcatch>
<cfcatch type="any">
<cfset rst = QueryNew('Error,Type','Varchar,Varchar')>
<cfset QueryAddRow(rst,1)>
<cfset QuerySetcell(rst,'Error',cfcatch.Detail)>
<cfset QuerySetcell(rst,'Type',cfcatch.Type)>
<cfrethrow>
</cfcatch>
</cftry>
<cfreturn rst>
</cffunction>
xxx.cfm contains:
<cfset xxxObj = CreateObject("Component","Components.xxx").Init(Application.DSN)>
<cfset xxxQry = xxxObj.View1()>
<cfif isDefined("xxxQry.Error")>
<cfoutput>
There is a #xxxQry.Type# error:<br />
#xxxQry.Error#
</cfoutput>
<cfabort>
</cfif>
Application.cfc contains:
<cffunction name="onApplicationStart" output="false" returntype="void">
<cfset Application.DSN = "myDatasource">
</cffunction>
You gotta see this !
(http://rayhorn.contentopia....
I'm glad Hal Helms warned me about posts like that at HelmsandPeters.com.
---------------------------------------------------------
The Diagnostic function inside of xxx.cfc will now change to:
<cffunction name="Diagnostic" access="public" returntype="string" output="True">
<cfset var result = "OK">
<cfset var xxxObj = CreateObject("component","Components.xxx").Init(Variables.DSN)>
<cfset var xxxQry = ''>
<cfset var xxxID = ''>
Foo
<cfset xxxQry = xxxObj.Foo()>
<cfif isDefined("xxxQry.Error")>
<cfset result="FAILED">
<cfreturn result>
<cfelseif xxxQry.recordcount EQ 0>
<cfset result="Empty">
<cfreturn result>
</cfif>
<cfreturn result>
</cffunction>
Now the question is whether to treat 0 records as an error or a warning.
We have a legend in our department that revolves around the boss giving tours of the data center.
Legend has it that he would always point to the monitor, which shows the status of every store on our network and proclaim
"Green is good, red is bad".
If you're of the "Green is good, red is bad" philosophy, then you may wan to treat 0 records as bad.
Since these diagnostics are individualized, you can decide with each function whether 0 records is a bad thing or not.
Oh! Oh! /me takes notes from Phillip.
Phill, I still say if you have a blog you should post this.If you don't have a blog, do you mind me posting an entry on this?
I don't have a blog yet.
I encourage you to post this anywhere as I'm trying to develop a standardized CRUD (Create, Retrieve, Update, Delete) system.
Since I'm just learning the cf syntax, I haven't had time to learn frameworks to determine if I'm reinventing the wheel, but that's OK since I have to learn the language anyway.
Raymond you are a paragon of self-control and an oak. I know that I shouldn't be giving him the satisfaction, but I take my hat off to you for being able to ignore the bitter, entirely unwarranted and frankly quite pathetic provocation (I mean, you could have been a coward and disabled the comments on your blog while taking public potshots at your 'tormentor' via other people's blogs). The person concerned is now really making a d***head of himself, and anyone performing due diligence on his name from now on will be uncovering this stuff. When in a hole some people keep digging; this guy seems to have rented a JCB.
Anyway, back to more constructive matters...
This is a test post. Please ignore.
The next thing to do is to not use cfquery at all!
The next step is to use a stored procedure.
I can hear what you're thinking: "All I want to do is a simple SELECT statement, and now you've got me doing all this".
Read the ColdFusion Developer's Journal article at
http://coldfusion.sys-con.c... to help justify why use a stored procedure instead of cfquery.
Here is the SQL Server code:
SET QUOTED_IDENTIFIER ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[xxx]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
DROP TABLE dbo.xxx
GO
CREATE TABLE dbo.xxx(
xxxID Integer Identity(10001,1) CONSTRAINT xxxID Primary Key Nonclustered,
xxxName Varchar(255) NOT NULL,
xxxDesc Varchar(255) NOT NULL,
xxxSort Int NOT NULL default 0)
GO
CREATE UNIQUE CLUSTERED INDEX xxxName ON dbo.xxx(xxxName)
GO
INSERT INTO xxx(xxxName,xxxDesc,xxxSort) VALUES ('Raymond Camden','Jedi Master',1)
INSERT INTO xxx(xxxName,xxxDesc,xxxSort) VALUES ('Phillip Senn','Padawan Learner',2)
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[xxxView]') and OBJECTPROPERTY(id, N'IsView') = 1)
DROP VIEW dbo.xxxView
GO
CREATE VIEW dbo.xxxView
AS
SELECT TOP 100 PERCENT * FROM xxx
ORDER BY xxxSort
/*
SELECT * FROM xxxView
*/
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[xxxSelect]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROC dbo.xxxSelect
GO
Create PROC dbo.xxxSelect(
@xxxID Integer=0
) AS
SET NOCOUNT ON
DECLARE
@Error Integer
IF @xxxID > 0
SELECT * FROM xxxView WHERE xxxID=@xxxID
ELSE
SELECT * FROM xxxView
SET @Error = @@Error
IF @Error <> 0 GOTO ProcErr
RETURN 0
ProcErr:
BEGIN
RAISERROR('There was an error in xxxSelect', 16, 1)
RETURN -100
END
/*
DECLARE @xxxID INTEGER
SELECT @xxxID=xxxID FROM xxx
EXEC xxxSelect @xxxID
EXEC xxxSelect
*/
GO
<!---
Application.cfc contains:
<cffunction name="onApplicationStart" output="false" returntype="void">
<cfset Application.DSN = "myDataSource">
</cffunction>
xxx.cfc contains:
--->
<cfcomponent displayname="xxx" output="False">
<cfobject name="DatabaseObj" component="Components.Gendbs">
<cffunction name="Init" output="False" returntype="components.xxx">
<cfargument name="DSN" required="yes">
<cfset Variables.DSN = arguments.DSN>
<cfreturn this>
</cffunction>
<cffunction name="View1" output="False" returntype="query" hint="I return everything from xxxView">
<cfargument name="xxxID" type="numeric" required="no">
<cfset var rst = "">
<cftry>
<cfstoredproc procedure="xxxSelect" datasource="#Variables.DSN#" debug="yes" returncode="yes">
<cfif isDefined("arguments.xxxID")>
<cfprocparam type="in" cfsqltype="cf_sql_integer" value="#Arguments.xxxID#">
</cfif>
<cfprocresult name="rst">
</cfstoredproc>
<cfcatch>
<cfset rst = QueryNew('Error,Type','Varchar,Varchar')>
<cfset QueryAddRow(rst,1)>
<cfset QuerySetcell(rst,'Error',cfcatch.Detail)>
<cfset QuerySetcell(rst,'Type',cfcatch.Type)>
<cfreturn rst>
</cfcatch>
</cftry>
<cfreturn rst>
</cffunction>
<cffunction name="Diagnostic" access="public" returntype="string" output="True">
<cfset var result = "OK">
<cfset var xxxObj = CreateObject("component","Components.xxx").Init(Variables.DSN)>
<cfset var xxxQry = ''>
<cfset var xxxID = ''>
View1
<cfset xxxQry = xxxObj.View1()>
<cfif isDefined("xxxQry.Error")>
<cfset result="FAILED">
<cfreturn result>
<cfelseif xxxQry.recordcount EQ 0>
<cfset result="Empty">
<CFELSE>
View1.1
<cfset xxxID = xxxQry.xxxID>
<cfset xxxQry = xxxObj.View1(xxxID)>
<cfif xxxQry.recordcount NEQ 1>
<cfset result="FAILED">
<cfreturn result>
</cfif>
</cfif>
<cfreturn result>
</cffunction>
</cfcomponent>
<!---
xxx.cfm contains:
--->
<cfset xxxObj = CreateObject("Component","Components.xxx").Init(Application.DSN)>
<cfset xxxQry = xxxObj.View1()>
<cfif isDefined("xxxQry.Error")>
<cfoutput>
There is a #xxxQry.Type# error:<br />
#xxxQry.Error#
</cfoutput>
<cfabort>
</cfif>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1...">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>xxx</title>
</head>
<body>
<table>
<tr bgcolor="cornflowerblue">
<td width="105">
Sort
</td>
<td width="152">
Name
</td>
<td width="407">
Desc
</td>
</tr>
<cfset Counter = 0>
<cfoutput query="xxxQry">
<cfset Counter = Counter + 1>
<tr <cfif Counter MOD 2>bgcolor="gainsboro"</cfif>>
<td>
#xxxSort#
</td>
<td>
#xxxName#
</td>
<td>
#xxxDesc#
</td>
</tr>
</cfoutput>
</table>
<!--- Diagnostic.cfm contains:
<html>
<body>
<table>
<tr>
<td>xxx:</td>
<td>
<cfset Obj = CreateObject("component","components.xxx").Init(Application.DSN)>
<cfset Diag = Obj.Diagnostic()>
<cfoutput>#Diag#</cfoutput>
</td>
</tr>
</table>
</body>
</html>
--->
</body>
</html>
So I know this entry is a few weeks old by now, but I have completed version 1.0 of a "var scope checker" like I briefly discussed in my earlier comments. This is a single CFC that reads a file or directory of files, analyzes all <cffunction> blocks, and returns an array of information about local variables that do not appear to be var-scoped.
Even though I am VERY careful about var-scoping, I ran this tool against some recent CFCs I built at work and was surprised to find a few loop indices had slipped through the cracks!
I am going to be setting up some CFUnit tests to run the var checker as part of my unit testing, I think that will help guard against non-var-scoped variables being introduced during code maintenance.
If anyone out there is interested (or even still reading this thread) check out the var scope checker at http://www.petry-johnson.co...
Seth,
Are you subscribed to 'CFCDev@cfczone.org'?
I know they would be interested in your var scope checker.
Phillip,
Thanks for your note! I am subscribed to that list and was planning on announcing the var scope checker once I fixed a few minor issues. For instance, a very helpful reader pointed out to me that the checker gives false positives when a var-scoped structure is accessed via array notation (like myLocalStruct["foo"]) instead of dot notation (myLocalStruct.foo).
I'd like to fix that and a few other minor things before inviting the entire CFC dev community to pick apart my work :)