NO MORE CAREER
POLITICIANS!
Get Out Of Our House: Replacing congress with TRUE citizens!
Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Web Hosting

best web hosting - top web hosting sites, thetop10bestwebhosting.com

Czech your Page Rank!
Check Page Rank of any web site pages instantly:
This free page rank checking tool is powered by Page Rank Checker service
Surf's Up!
Visit Egosurf.org and massage YOUR web ego!
My Score: 9,001
Doug's Books

Read (and recommend)

  • Men are from Mars, Women are from Venus
  • The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How Collective Wisdom Shapes Business, Economies, Societies and Nations
  • Blink: The Power of Thinking Without Thinking
  • Head First Design Patterns
  • Transact-SQL Programming
  • What's So Amazing About Grace?
  • Just So Stories (Rudyard Kipling collection)

Reading

  • Prayer: Does it Make Any Difference?
  • Data Mining (Practical Machine Learning Tools and Techniques)
<< September, 2010 >>
SMTWTFS
1234
567891011
12131415161718
19202122232425
2627282930
Search Blog

Recent Comments
Re: Using Google as your CF Mail Server (by Mike at 9/07 4:02 PM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Nithin Chacko Ninan at 9/07 1:34 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Nithin Chacko Ninan at 9/07 1:33 AM)
Re: Configuring Apache To Use Multiple Versions of ColdFusion (by Lola LB at 9/06 6:28 AM)
Re: Configuring Apache To Use Multiple Versions of ColdFusion (by ComboFusion at 9/06 5:17 AM)
Re: Railo 3.1 on Windows Server 2008 and IIS7 - Part 3 of 3 (by Jon at 8/27 2:04 PM)
Re: Hosts File Changes Not Acknowledged on Vista 64 (by Spacy at 8/24 3:46 PM)
Re: THE DAY CFUNITED DIED (by ComboFusion at 8/23 10:50 AM)
Re: My Grandpa (by Tasha at 8/10 4:29 PM)
Re: Just What IS a 'Service Layer', Anyway? (by dougboude at 8/02 10:10 AM)
Categories
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

06 September 2010
Configuring Apache To Use Multiple Versions of ColdFusion

This post is for those of you out there who have managed to get two or more versions of Coldfusion running side by side, but are now facing the challenge of HOW on earth you can actually run your apps in both versions using Apache. For example, now you'd like to test your app in CF9 by typing in "localhostCF9/myKillerApp" and in CF8 by typing in "localhostCF8/myKillerApp".  In theory, this is a no brainer, but when it comes down to answering the question of "okay...how do i configure my dev environment to allow me to do such a thing?", this is where I found myself in a fog.

Before you go on, you should know that locally I use Apache as my web server, not IIS, so this post is heavily focused on Apache only. If any of you have accomplished the topic of this post using IIS, feel free to leave a link in the comments!

PREREQUISITES TO THIS POST

This post assumes that you're already at a certain point: Apache is installed and working, and you have multiple instances/versions of CF installed and working. If you DO meet this criteria, you may want to just skip to the "Tying it All Together" section. If not, I have info and recommendations to share regarding meeting this criteria.


STARTER RECOMMENDATIONS, IF YOU NEED THEM

Although my development environment is Windows, I'm not using IIS as my local web server, but rather Apache. If you don't already have Apache installed and/or are not an admin type who enjoys digging in to config files, I highly highly recommend that you install a little product called WAMPServer (http://www.wampserver.com/en/download.php). It is an uber simple app that will install Apache, Mysql, and PHP on Windows, and then provide an easy interface for managing those products. From one tiny interface you can restart services, add/change Apache, PHP, and MySQL versions, edit config files, and add/remove PHP modules. In our case (CF lovers), PHP is ONLY relevant because that is what drives PHPMyAdmin, the app included with WAMPServer that provides a nice easy web-based interface to your MySQL installation (I use it heavily).

If you haven't already gone through the exercise of installing multiple versions of CF, and since that IS your first step in all of this, I highly recommend the lovely Daria Norris' tutorial (found here http://www.cfgothchic.com/blog/post.cfm/my-cfmeetup-presentation-installing-multiple-versions-of-coldfusion) as THE place to start. A couple of things that I did while following her instructions that you may also want to bear in mind:

1. during the install, when you get to the part about connecting to existing web servers, just tell CF to use its own internal server;
2. One thing Daria mentioned briefly that I didn't catch until I listened to her presentation a second time is that you should not specify a root context when installing your instance of CF8; just leave it blank (unless you really understand what that means and are comfortable changing it). If you don't know what a root context even is, no biggie; you'll encounter the question that uses the phrase, and you'll now know to just leave it blank.

MAKE SURE CF IS WORKING RIGHT

Okay, so let's make the assumption that at this point you have installed CF9 and have two instances running: one that is CF9 and another that is CF8. At this point you will have three different admins: one that is the 'super' admin for the master enterprise installation of CF9; one for the new instance of CF9 that you added; and one for the instance of CF8 that you added. Since we told CF that we were using the internal web server, you should be able to reach each of these now via URLs that resemble the following: http://127.0.0.1:8300/CFIDE/administrator/index.cfm, where port 8300 = the super installation, 8301 = the CF9 installation, and 8302 = the CF8 installation. Once you can hit the admins for each install using those URLs, you're ready to proceed. To verify what the correct port numbers are, you can look in the "Instance Manager" portion of the super admin interface

coldfusion 9 administrator showing the instance managerThe CF9 Instance Manager

MAKE SURE APACHE IS WORKING RIGHT
Let's also assume that you have created a URL that Apache uses to hit the local web site we will be using for testing. This post won't delve deep into the details of doing that, but I will share the following snippets.

1. line in my hosts file (C:\WINDOWS\system32\drivers\etc\hosts):

127.0.0.1       localhost

2. line block in my apache config file  :

<VirtualHost *:80>
    DocumentRoot "D:/Inetpub/wwwroot"
    ServerName localhost
</VirtualHost>

(how to edit the config file when using WAMPServer)

Now, place a simple html file at the path you specified as the DocumentRoot above. Make sure you restart your apache server after making the change (as demonstrated in the video above), and close and re-open your browser so it will see the changes to the hosts file. After that, you should be able to browse to http://localhost/[yourtestpage.html] and view it. This will tell you that Apache is working fine and the virtual host is configured correctly.


TYING IT ALL TOGETHER

SO, how do we get CF and Apache talking? The big picture answer is, by telling the JRun server to create a ColdFusion proxy for us that Apache can use to pass requests to for processing. After we get the proxy created, we will then create the Apache virtual hosts we want and instruct Apache to use the proxy for those virtual hosts, handing off requests to the appropriate CF instance.

1. Create the Proxy

The way to get this proxy created is to run the Web Server Configuration Tool. You will find it under Start/Programs/Adobe/Coldfusion 9 (multiserver version)/Web Server Configuration Tool , or you can go directly to C:\JRun4\bin\WSConfig.exe to kick it off. Just click 'Add' and tell it about your web server.

(Hint: If you DID opt to install WampServer, your Apache config file will be found at C:\wamp\bin\apache\Apache2.2.11\conf , and your Apache binary (asked about when you click on the 'Advanced' button) is at C:\wamp\bin\apache\Apache2.2.11\bin\httpd.exe. Do browse these first to verify the paths, just in case yours is a little different)


2. Verify Your Proxy Creation

After you finish, the way to verify that all went well is to take a peek in the folder C:\JRun4\lib and make sure a sub folder called "wsconfig" was created, that itself contains a subfolder (named 1, or 2 probably). If those exist, so far so good.

3. Verify Apache Proxy Configuration

The web server configuration, in addition to creating the proxy, also touched your Apache configuration file and added a few lines. Open your Apache config file (like you did earlier when you created the virtual host) and take a gander near the bottom. Those lines should look similar to this:

# JRun Settings
LoadModule jrun_module "C:/JRun4/lib/wsconfig/2/mod_jrun22.so"
<IfModule jrun_module>
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000
</IfModule>

I want you to ignore the "loadModule...." line, BUT remove (copying and saving somewhere temporarily...very important!) all of the JRunConfig lines. After you have removed those lines, save your config file and restart Apache, just to make sure the config file is good up to this point.

(note: IF you wish to have a default version of CF to be used when NOT utilizing one of the version-specific domains/virtual hosts we will be creating, then LEAVE this section of code in place)

4. Make a Tweek to the JRUN.XML Files
Each of the ColdFusion instances that you are running can be found by looking in the  "C:\JRun4\servers" folder. You should see folders there named according to each of the instances you created before. Mine are named CF8 and CF9. Inside of each of those folders, under SERVER-INF, you will find the jrun.xml file (path to my CF8 jrun.xml: C:\JRun4\servers\CF8\SERVER-INF\jrun.xml). For each instance's jrun.xml file, we are going to open it up and make one change, and note one setting within it.


Change 1: Search for the jrun proxy section ("<service class="jrun.servlet.jrpp.JRunProxyService" name="ProxyService">"). Within that section is a setting called "deactivated"; make sure it is set to 'false' (<attribute name="deactivated">false</attribute>).

Item to Note: In that same section, note the value of the 'port' attribute. You will need to know this port number in order to tell Apache what version of CF to use for a specific virtual host. In my case, the attribute looks like this:

<attribute name="port">51000</attribute>

5. Create Your Own Virtual Hosts for Each Version

Okay, finally down to last step: creating domains/virtual hosts that will both point to the same content, yet serve it up using a different version of ColdFusion! We first need to decide on a domain name for each version. Myself, I chose to use "localCF9" and "localCF8" as my CF-specific domain names. So, step 1 in creating a new virtual host is to edit my hosts file. I added the following lines:

127.0.0.1 localCF9
127.0.0.1 localCF8

Save hosts, making sure you don't inadvertently save it as 'hosts.txt'...if using notepad, make sure you do a 'save as' and change the type to 'all files'). Now, let's edit our Apache config file again, adding two more virtual hosts blocks that resemble the following:

<VirtualHost *:80>
   ServerName localCF9
   DocumentRoot "D:/Inetpub/wwwroot"
</VirtualHost>

<VirtualHost *:80>
   ServerName localCF8
   DocumentRoot "D:/Inetpub/wwwroot"
</VirtualHost>

You'll notice that I am pointing both virtual hosts to the same folder as its root...the root of my local environment. I am doing this because I want ANYTHING I browse to via the 'localCFx' domains to be dealt with by the appropriate CF versions. Before you go on, it's a good idea to restart Apache again and just make sure you can browse to localCF9/[yourtestpage.html] and localCF8/[yourtestpage.html] just to make sure all is well with the virtual host configuration. Remember to close and reopen your browser so it 'sees' the changes to hosts file.

6. FINAL STEP
The final step is for us to inform Apache that, anytime it receives a request for a particular virtual host, it needs to hand that request off to a specific Coldfusion version via the proxy that was created for us. Go back and grab the settings you saved from the config file earlier, the ones that look like

    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000

and paste it INSIDE of each of your virtual host blocks for localCF8 and localCF9. Then, change the Boostrap port number to match what you found in the jrun.xml files for each instance. Here is my final virtual hosts blocks:

<VirtualHost *:80>
   ServerName localCF9
   DocumentRoot "D:/Inetpub/wwwroot"
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51000
    AddHandler jrun-handler .cfm .cfml .cfc .jsp
</VirtualHost>
<VirtualHost *:80>
   ServerName localCF8
   DocumentRoot "D:/Inetpub/wwwroot"
    JRunConfig Verbose false
    JRunConfig Apialloc false
    JRunConfig Ignoresuffixmap false
    JRunConfig Serverstore "C:/JRun4/lib/wsconfig/2/jrunserver.store"
    JRunConfig Bootstrap 127.0.0.1:51002
    AddHandler jrun-handler .cfm .cfml .cfc .jsp
</VirtualHost>

Notice they both use the same jrunserver store, but they each are 'talking to' the proxy via a different port. This port number is what tells JRun which instance should receive the request. Also notice that I added an "AddHandler" line to each of the blocks; without this, Apache treated my cfm files like text files. Also, if in step 2 you left a default version of CF in place, you may also have to add the "AddHandler" line to that section if CFM templates aren't served up as expected.


THE FINAL TEST

As a final test, I created a cfm page that dumped the SERVER scope AND dumped an object that I created using a simple CFC that contained only a single "init" method that returned 'this'. I recorded the demo for you to see, highlighting the cf version in each.


FURTHER READING
These sites, along with Daria Norris' tutorial on initially setting up the multiple versions, were of great help to me! And a special thank you to the awesome Mr. Stephen Moretti who managed to get me on the right track initially and who wrote one of the blog posts below.

http://www.cfgothchic.com/blog/post.cfm/my-cfmeetup-presentation-installing-multiple-versions-of-coldfusion


https://www.adobe.com/livedocs/jrun/4/JRun_Administrators_Guide/connectors2.htm


http://nil.checksite.co.uk/index.cfm/2006/5/3/cfmx-multiserver-set-up


http://www.simonwhatley.co.uk/configuring-coldfusion-8-with-apache

Posted by dougboude at 3:37 AM | PRINT THIS POST! | Link | 2 comments



31 July 2010
THE DAY CFUNITED DIED
The Summation of My Experience

As you ALL know, this was the last CFUnited ever, and as an attendee, I am certain that this fact was actually a positive influence on how the conference played out. The camaraderie that is always an integral part of this event was magnified by the ever-present fact that we just don’t know when we may be able to assemble ourselves in these numbers again. I know for myself (and I do believe that I am representative of most of my CF brethren), I felt it to be even more important this year to seek out and get to know new faces, and to push myself outside of my hermit-like inclinations and spend good quality social face time.  During said face time, we all engaged in the discussions of the subjects that this conference’s demise has brought to the forefronts of our minds. Why was it ending? What did it mean for our community? What would be the ramifications? Was it a sign of anything to come? as confident as we are about the future of the language we embrace and earn our livings with, the demise of the grandest exclusively CF event to grace our professional landscape in the past six years has definitely been a cause of at least minimal concern to many. But, through the process of the fellowship, the sessions, the after parties, and the after parties’ after parties, I gained an absolute reassurance that what we have witnessed is nothing more than the results of an economy that’s been on a downward trend for the past three years and is in no way a prognostication of the future of ColdFusion. In fact, what it DOES mean is that now that one of the larger trees in the forest has sadly fallen, the vacancy it leaves us with is the opportunity and catalyst to allow for the currently smaller, more focused, and/or regional conferences to flourish and fill this niche in our professional ecosystem. I firmly believe this, and have found nothing but reassurance for this belief in the past three days of being immersed among my peers and mentors.

ON THE NEGATIVE (BUT POSITIVE) SIDE...

Having highlighted how excellent the conference was, I feel the need to share a less positive opinion based on my observations this year as an attendee. This particular CFUnited seemed to greatly lack in its administration as compared to those in the past that i have attended. The staff seemed more interested in doing their own thing as opposed to being focused on the details relevant to a conference, such as session start/stop times, ensuring sound levels, recording speakers, ensuring that the doors were closed once a speaker began, giving the speaker cues as to when his or her time was nearly up, and just maintaining a positive, prominent presence throughout the event. Fortunately, and I give HUGE kudos to these individuals, there were those individuals who were in attendance that took great responsibility upon themselves to fill in these gaps. They ensured that the registration desks were properly manned and that the attendees were given the proper attention at registration, they went the extra mile and turned what would probably have been blas’e social events into OUTSTANDING social events, they invested their own time to make certain that the fellowship, the tradition, and the kindred nature that we all share as a CF community was recognized and honored. I would give specific shout outs to those who I am aware were instrumental in making this conference as memorable and awesome as it was, but truth be told, everybody in attendance had some hand in that. Together, we made this CFUnited what it should be, and we can be proud of that fact. On the other hand, the actual organizers of the event...honestly, they couldn’t have seemed more disinterested to me. Oh, and the fact that I was solicited via email to help "save" CFUnited by BUYING THE RIGHTS TO IT??? Man, that's like telling your kid you love him and then putting a price tag on his forehead. That's just wrong in my book. Like I said, just my observation and opinions here.

 LOSING MY VIRGINITIES

This conference also happened to be one that resulted in a few major changes for me. Besides the hardly containable inspiration that the sessions were regarding things like alternatives to RDBMS, iPhone development, the exploration of HTML5, and a new found interest in the CF on Wheels and FW/1 frameworks, I also found myself doing things that I was certain I never would do. I, a profoundly staunch PC, discovered that I am now “i-curious”. I want a mac. It stuns me and frankly, freaks me out, every time i hear myself say such a thing, but it’s true: i want a mac. Any of my friends who know me very well are probably picking their jaws up off of the floor at reading that, as I have never even come CLOSE to waivering in my unrelenting despisings (and uninhibited vocalization OF those despisings!) of all things mac. Heck, when they first saw me palming an iPhone they nearly fainted. But after seeing the presentation on iphone development with the Coldbox framework as the backend data services, something just gave up inside of my head and I, for the first time in my life, understood what it felt like to desire a mac. I know, this almost sounds like I’m coming out of the closet, and in many ways it certainly does feel that way to me, too! A strange feeling, like I’m giving in to the thing I shunned for so long...I’m being dramatic, huh?

Oh, the second thing I did that I said I would purposefully NEVER do: I had my picture taken with Ben Nadel. Again, as any of my friends who know me well will attest to, I am in general an “anti-herd” guy; if everybody’s doing it, then to me that’s a sure sign that they’re doing something i do NOT want to do. And, since everybody seemed to be giddy at the idea of having their picture with Ben up on his site, I naturally vowed internally never to do so. But then, I actually got a chance to meet Ben and talk with him a little bit. Gosh darn it, he’s just too nice! :) So, I conceded and participated in a three way with him and Ryan Jeffords. Three way photo opp, that is. :)

WHAT NOW?

So NOW then... let’s all set our sights on the next conference or conferences we’re going to attend, shall we? Our time honored tradition of supporting our community by communally sharing what we all learn individually must continue, and it will do so in the form of ALL of us making it a point to contribute and participate in whatever ways we can. BLOG, y’all. BLOG the things you learn that have made your development life easier. TWEET. Share the tiny 140 character moments of your days that reveal your knowledge, wit, charm, losses, gains, wisdom, and personality to the rest of us. SPEAK. Overcome your erroneous belief that you have nothing of value to offer your peers and mentors and volunteer to speak at your local user group, the online cfmeetup group, other user groups in other cities, and conferences. When you get the word that such and such conference is having a call for speakers, you should be prepared to submit yourself and two or three topics that you have already presented on via the user groups. I know it can be difficult to overcome shyness and such, but believe me, all of us have something of value to offer. Heck, even the very fact that someone who never spoke publicly before is making that effort is in itSELF an inspiration to others, regardless of the topic or how smooth the preso goes!

Oh, let me leave you all with this awesome resource that Charlie Arehart maintains for the sake of his community: http://www.carehart.org/cf411/  There, you can find out about all the latest conferences and other resources whereby a CF developer can get “plugged in” to his or her community and begin partaking and sharing.

CONCLUSION

This conference has left me with a LOT of new friends, personalities and faces to connect with twitter icons, a boat load of technical inspiration, and the loss of at least two virginities. CFUnited 2010 rocked, y’all! If you missed it, you really did miss a lot.

I look forward to crossing paths with you in the future! In the meantime, if you're not already, let's be virtual buddies, eh? My twitter name is dougboude

Posted by dougboude at 1:27 AM | PRINT THIS POST! | Link | 1 comment
13 July 2010
PHP vs COLDFUSION
a brief, biased comparison

I had the privilege of doing a short presentation at our local CFUG meeting last night, the topic being "Coldfusion Vs PHP", so thought I'd share the slides and some brief commentary on some of them. We didn't record the meeting, so what you're about to see is an abbreviated version.

Posted by dougboude at 12:43 PM | PRINT THIS POST! | Link | 9 comments
17 December 2009
Using a CFC as a Configuration File
Leveraging The Decorator Pattern
SO, you too find yourself needing to write something that could really benefit from having its settings contained in some external resource. Let's see, what are the choices...we could possibly store our settings in the database and retrieve them at application initialization, if our architecture permits that; we could create an xml file that contains our settings in a nice, nested, readable manner; we could (as one person I mentioned this to suggested) put our settings into an INI file (ya think this person does a little too much .NET? :) ); or we COULD, as I have recently become enlightened to, put our settings into a CFC. Since I am especially partial to the idea of being able to have the choice of hard coding my settings and/or leveraging Coldfusion's dynamic personality to populate them, I went the way of using a CFC.

Allow me to preface the remainder of this post by saying thank you to those out there who have already traveled this road and have made their work open source. By dissecting several different code samples and doing a lot of experimentation, I have been able to enlighten myself as to the mechanics and theory behind it.

SCENARIO
For the sake of illustration, let's say I'm writing a plugin that will be used to provide shopping cart functionality to whatever app is implementing it. Of course, every app's needs, environment, and settings are different, so our plugin needs to be configurable. My goal then is to create a CFC that will contain a nested structure/array of settings that my shopping cart plugin CFC can utilize to control how it is implemented. Sounds simple, right? Or is it... :)

For those who want to skip right to the heart of the matter, you may do so now. For those who, like me, enjoy the layered learning process and the excitement of examining the pieces before putting the puzzle together, let's take a quick look at essential the concepts that allow using a CFC as a configuration file to work.

THE ESSENTIAL CONCEPTS

SCOPE
You already know about scopes in CF and are well versed in how and when to use them, I'm sure. You know, for instance, that if you create a variable within a template and ommit the scope, it is automatically put into the variables scope. You should also be aware, however, that any functions you create within your template also occupy a place within the variables scope. Take this template, for instance, and the accompanying dump:
<cfset thisvar = "Hello" />

<cffunction name="returnIt" access="private" returntype="any">
    <cfargument name="incoming" type="any" />
    <cfreturn arguments.incoming />
</cffunction>

<cfdump var="#variables#" label="MyTemplatesVariables" />


dump of variables scope in a coldfusion template

Interesting, eh? Keep this in mind.

Now, you may also already be aware of the fact that within an instantiated CFC you have scopes as well. I won't spend any time elaborating on those scopes or the differences between them (did that in another post a while back). But I WILL hit you with a short CFC and a dump of its own internal variables scope. Based on our little experiment above with our template, look at the CFC internals and formulate an idea of what you would expect to see. You may see something surprising in this one.

Component A.cfc
<cfcomponent output="false">
    <cffunction name="Foo" access="public" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="Man" access="private" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="Chu" access="remote" output="false" returntype="any">
        <cfargument name="nuttin" type="string" />
        <cfreturn arguments.nuttin />
    </cffunction>
   
    <cffunction name="getMyVariablesScope" access="public" returntype="any">
        <cfset var theseVars = "" />
        <cfsavecontent variable="theseVars">
            <cfdump var="#variables#" label="ComponentAVariables" />
        </cfsavecontent>
        <cfreturn theseVars />
    </cffunction>
</cfcomponent>


Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>


dump of variables scope from within a coldfusion cfc

As you probably expected, there in the variables scope are all the functions of the CFC (regardless of what access type we gave them). Additionally, we have the often shunned THIS scope...and looky what THIS is harboring. That's right boys and girls, a reference to (not copy of) the CFC instance. THIS, as you may or may not know, IS accessible from outside the CFC instance. So if we modify slightly the code we used above to instantiate the object, and add a line to give our object another attribute, a subsequent call to getMyVariableScope will reflect the results.

Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfset objA.someFunkyParam = "Doug Boude Rocks" />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>


dump of variables scope from with a coldfusion cfc

This is perfect behavior for what we're wanting to do! Oh...what is it we are wanting to do? Let's get to the next and last essential concept and see!

THE DECORATOR PATTERN
Here's a term I'm sure most of you have heard of. If you haven't heard specifically of this pattern, I'm sure you've at least heard of design patterns in general. Well, you can and should think of this one exactly as its name sounds. Let's say you figure out that the fastest way to meet chicks is to buy a dog (since you can't afford to buy a baby), so you grab a "mildly malformed" West Highland White terrier from the "we finance anyone" pet store down the street. You invest a few more bucks and take little Timmay to the groomer. When you pick him up later, he is now sporting a lovely red tartan neckerchief and smells of Eau de Tim McGraw. Timmay has been decorated! He's still a midly malformed Westie, only NOW he ALSO has been given the ability to attract hot chicks, AFTER he was born. I know, it was an elaborate analogy, but this is how I entertain myself. So then...since in our mind's eye we want our Config.CFC to be beautifully simple for the end user to populate and we don't want them to have to ignore (and try not to touch) lots of other support methods or worry about inheritance dependencies, what we ideally need to be able to do is provide them a clean, simple CFC that contains only a configure method. When they populate it (or code it to grab its values from elsewhere), we'll take it and then DECORATE it with a method that will allow us to retrieve that same configuration info! Okay, enough of concepts and ideas. On to the solution.

THE SOLUTION
The key players in this scene are:
"ShoppingCart.cfc", a plugin designed to provide shopping cart functionality to any application;
"Config.cfc", a cfc whose sole purpose in life is to provide the developer with a single place in which to maintain relevant ShoppingCart settings;
"configTest.cfm", a cheesy template who just kinda glues this stuff together for us.

First, configTest.cfm.
<cfset objShoppingCart = createObject("component","ShoppingCart").load("Config") />
<cfdump var="#objShoppingCart.getSettings()#">


Straightforward. We're creating our shoppingcart cfc, calling the load method and passing in the class path to our configuration cfc. Then, calling shopping cart's getSettings method just to prove to ourselves that it worked.

Config.cfc.
<cfcomponent output="false">
    <cffunction name="configure" access="public">
        <cfscript>
            settings = {
                environment = {
                    DSN = "dbserver",
                    EmailServer="mail.onelove.com"
                },
                cartSettings = {
                    maxCartItems = "15",
                    salesTax = "true",
                    cartHeading = "One Love to Rule Them All",
                    cancelMessage = "Aw baby, why you leavin?",
                    thankyouMessage = "Stay Cool",
                    daysLeftTillXmas = ceiling(datediff("h",now(),createdate(2009,12,25))/24)
                }
            };
        </cfscript>
    </cffunction>
</cfcomponent>


This is a "hard coded" version, where all the settings are just there. You'll notice at least one of them taking advantage of a PRIME reason to do configuration this way: dynamic values!

ShoppingCart.cfc (scaled down to ONLY the relevant methods needed to illustrate the point of this blog post).
<cfcomponent output="false">
    <cfset variables._settings = "" />
   
    <cffunction name="load" access="public" returntype="any" hint="I load settings from an external CFC">
        <cfargument name="configpath" type="string" required="true" />
        <cfscript>
            var objConfig = createobject("component",arguments.configpath);
            //need to run objConfig's configure() method in order to actually create the
            //configuration settings structure
            objConfig.configure();
           
            //decorate objConfig with our local _getConfig  method :)
            objConfig.getConfig = variables._getConfig;
           
            //execute the method we just added to objConfig to retrieve objConfig's 'settings' struct
            variables._settings = objConfig.getConfig();
           
            return this;
        </cfscript>
    </cffunction>
   
    <cffunction name="getSettings"
        access="public"
        returntype="any"
        hint="I am the method used to get our local configuration values.">
           
        <cfreturn variables._settings />
    </cffunction>
   
    <cffunction name="_getConfig"
        access="private"
        returntype="any"
        hint="I am the private method that will decorate our configuration object!">
       
        <!--- we're counting on our configuration object to have a variable called 'settings'... --->   
        <cfreturn variables.settings />
    </cffunction>
   
</cfcomponent>


You'll want to study this one a bit, and let me point out a few things.

The last method, "_getConfig", serves no other purpose than to decorate our configuration object. Now, you may be thinking, "but I could just put that method inside my configuration object and tell my users to just ignore it!". Yeah, you could; but that's cheesy. So, to avoid smelling of aged Camembert, we hide this method here and add it to our configuration object within the "load" method.

Here's the results of calling configTest.cfm:
dump of coldfusion structure

SUMMARY
In a nutshell, because an object always sports a THIS scope, AND because local functions always exist in the VARIABLES scope, it is a simple matter to attach a new method to an object. If the method you are attaching is written correctly, it can access the methods and values within the object that all of the native methods can. By leveraging this very cool relationship, you can move configuration settings to a CFC.


POST SUMMARY
You may also be thinking, "Dude, I or someone else I don't trust could wreak so much havoc in my code by decorating objects with well-written methods!". And dude, I do believe you're probably correct. I haven't researched much into the security ramifications, or how many ways one might find to defile the sacraments of "clean OO"; but, I know that with CF comes great power, and with great...oh, you know. We've chosen the more lenient world of CF, so it's up to us to respect those freedoms and work with them accordingly. If you don't feel good about that, well, there's always the more communistic "typed" languages, with their greater complexities and unbending rules. :) Hey, some people prefer domination ;) To each his own.
Posted by dougboude at 12:41 PM | PRINT THIS POST! | Link | 0 comments
30 November 2009
PayPal IPN Coldfusion CFC
ppIPN
Thursday morning I overheard our finance lady mentioning an online form she had created to handle registrations for a conference we're hosting in a few months. My curiosity piqued (since usually things like this end up on my plate, at least in part), I inquired further and found that she was also including a "Pay Now" PayPal button so the registrants could complete the process. I decided to make some work for myself and suggested that we save the registration information to our database (as opposed to just emailing form content to designated people, as was the plan), and leveraging PayPal's Instant Payment Notification (IPN) service to automatically capture payment information as it happens.

In a nutshell, PayPal's IPN works like this:

1. A customer creates an order record on your site and is then sent to PayPal to provide payment for the goods or services they are purchasing from you;
2. Upon completion of their payment, a "silent ping" takes place from PayPal to the URL of your choice. This ping contains several pieces of information about the transaction.
3. The code at the URL you told PayPal to ping then pings PayPal back again with the exact same information posted to it; If the information you sent back is valid, PayPal will respond simply with the word "Valid"; if the information you pinged back was NOT valid, PayPal will respond with "Invalid".
4. If your response from PayPal was "Valid", you should feel good about proceeding with the update of your customer's purchase record in the database using the information contained in the original ping; If the response was "Invalid", handle that as you will.


Even though the process is fairly simple, writing the code to deal with IPN can be somewhat challenging, made mostly so by the fact that it's an "invisible ping". Troubleshooting any kind of asynchronous process (which is what IPN is) requires special approaches. So, since I already went through the process to produce what I call "ppIPN.cfc", I figured I'd go ahead and share it (if you want, you can skip directly to the download). I tried to keep it easily configurable to accommodate different apps, but at the very least it's a good set of starter code if it doesn't meet your needs as is.

ppIPN.cfc actually consists of TWO CFCs: ppIPN.cfc and ppSettings.cfc, which ppIPN extends. As you probably guessed, ppSettings is where you will put the different variables that affect how your implementation of ppIPN will work. I made it a separate CFC so that you didn't have to maintain potentially sensitive information within the one component, and so that you could put ppSettings wherever your heart desires.

USE

Note:
ppIPN assumes three things:
1. that a record for a customer already exists in your table, and the payment notification ping from PayPal is for the purpose of updating that existing record with transaction details;
2. that you are sending the ID of your customer's transaction record to PayPal by using PayPal's "CUSTOM" form field (<input type="hidden" name="custom" value="16" /> or ...&custom=16&....)
3. your cfmail tag doesn't require a mail server, username, or password to be specified (they are already set up in your cfadmin)

To use ppIPN, simply place it and the corresponding ppSettings.cfc on your webserver where they can be called via http. Adjust the 'extends' path in ppIPN.cfc if you located your ppSettings.cfc anyplace other than the same folder where ppIPN.cfc lives.

Next, edit your PayPal account to enable IPN and enter the following URL as your post back URL:

http://[your web server]/ppIPN.cfc?method=postback

Lastly, edit your ppSettings.cfc to accurately reflect your environment.

That's it!

Here are the settings and a sample:

dsn - name of the datasource you wish to use
receiverEmail - the primary email address associated with your paypal account. (Though there can be several email addresses associated with your account, it is the primary email address that will be included in the IPN call from paypal, regardless of which address the payment was actually sent to)
notifyEmails - comma delimited list of email addresses you wish for notifications and status messages to go to
adminemail - the email address that will be used in the FROM of emails sent by ppIPN
ppURL - the url to use when re-posting data back to PayPal for verification

tblPayment - name of the table you're using to store transaction information
fldID - the name of the identity field for the table named above. ppINC assumes this will be an autoincrementing integer
fldFirstName - the name of the first name field in the tblPayment table. This will be the purchaser's first name.
fldLastName - the name of the last name field in the tblPayment table. This will be the purchaser's last name.
fldTransactionID - the name of the field in tblPayment in which you will be storing PayPal's transaction ID value. Should be varchar.
fldPaymentAmount - the name of the field in tblPayment in which to store the payment amount (PayPal's 'mc_gross' variable value). ppINC assumes this field is of type DOUBLE
fldPaymentDate - name of the field in tblPayment to store the transaction date. ppINC assumes type DATETIME


Here is a sample ppSettings.cfc:
<cfcomponent output="false">
    <cfscript>
        instance = structnew();
        instance.settings.dsn = "absolutezero";
        instance.settings.receiverEmail = "sclause@northpole.com";
        instance.settings.notifyEmails = "sclause@northpole.com,rreindeer@northpole.com";
        instance.settings.adminemail = "administrator@northpole.com";
        instance.settings.ppURL = "https://www.paypal.com/cgi-bin/webscr";
       
        //DATABASE SETTINGS
        instance.settings.tblPayment = "REGISTRATIONS";
        instance.settings.fldID = "ID";
        instance.settings.fldFirstName = "FIRSTNAME";
        instance.settings.fldLastName = "LASTNAME";
        instance.settings.fldTransactionID = "PPTXID";
        instance.settings.fldPaymentAmount = "PMTAMOUNT";
        instance.settings.fldPaymentDate = "REGDATE";
    </cfscript>
</cfcomponent>


TESTING
Here is a sample form that can be used to test your installation of ppIPN outside of using a PayPal Sandbox (feel free to add additional PayPal fields, but currently these are the only ones ppIPN looks for). To ppIPN, this form's post appears the same as a post from PayPal:

<html>
    <head>
        <title>
       
        </title>
    </head>
    <body>
        <form action="ppIPN.cfc?method=postback" method="post">
            <input type="hidden" name="payment_status" value="completed" />
            <input type="hidden" name="mc_gross" value="155.00" />
            <input type="hidden" name="txn_id" value="asdfsdgerweresfggzz" />
            <input type="hidden" name="receiver_email" value="rniemeier@masonclaims.com" />
            <input type="hidden" name="payer_email" value="dougboude@gmail.com" />
            <input type="hidden" name="custom" value="16" />
            <br>
            <input type="submit" value="submit" />
        </form>
    </body>
</html>


HOW PPIPN WORKS
1. Your customer performs a checkout on your site, creating a record in your transaction table without a payment status. Via whatever mechanism you select, they are sent to PayPal to complete the transaction, along with the ID of their record tucked within the "custom" field. VERY IMPORTANT: DO pass along the ID for the transaction record in the CUSTOM field!
2. Customer completes their transaction on PayPal. PayPal pings ppIPN with several different values.
3. ppIPN captures the incoming values, arranges them into a query string, and posts them back to PayPal.
4. PayPal validates the values ppIPN passed back and responds with either "Valid" or "Invalid".
5. If "Invalid" is received, ppIPN wraps up all the relevant values received and emails them to the addresses specified in the "notifyEmails" setting.
6. If "Valid" is received, ppIPN performs several additional checks:
  is the status 'Completed'?
  does this a unique transaction id that does not already exist in the db?
  is the incoming receiver email value equal to that specified in the settings?
If the answer to the above is 'yes', we proceed to update the db. Otherwise, we put together a nice status email and send it out.


ADDITIONAL VALUES FROM PAYPAL

There are many variables that get returned to you via IPN calls from PayPal; ppIPN only captures and saves a few of them. To find out more about the other variables available to you, you can visit this page on PayPal's site: http://bit.ly/paypalIPN

If you wish to capture more values than what ppIPN is currently capturing, it will require some editing of the cfc. To save you some time, here are some hints on what you'll need to touch should you desire to alter what data is currently persisted:

1. Edit ppSettings.cfc and add the additional fields you want to capture (use existing settings as a guide);

2. Edit the query in ppIPN.getPaymentRecord; add your additional fields to the SELECT clause (using the variable names, not the variable values); Be SURE to alias those fields!

3. Add code in ppIPN.doValid, just below line 113, to set the values you wish to capture. In this area, you will utilize the Alias values you created in step 2. Note: All incoming PayPal variables are initially captured to the global structure "instance.incomingArgs", so any and all values that arrived in the call will be there.

Here is how to capture a new value for persistence:
<!--- set this record's value for txn_type... --->
<cfset existingPaymentRecord.tran_type = instance.incomingArgs.txn_type />

In this example, tran_type is the alias we gave to our transaction type field. The corresponding value we want to save coming from PayPal was captured in the instance.incomingArgs.txn_type field. txn_type is the name PayPal provided, and can be found in the IPN documentation.

4. Edit ppIPN.updatePaymentRecord, altering the query to include your new fields. You can use the existing field/value pairs as an example for adding additional ones.

That's it!

Hope this saves someone a little time.

Download the ppIPN ZIP
Posted by dougboude at 11:11 AM | PRINT THIS POST! | Link | 6 comments
25 August 2009
Strange Validation Issue with CFARGUMENT

I was chatting with a buddy of mine today about an issue he had encountered when passing a list of numbers into a cfargument whose type was set to "numeric" (CF 8). For whatever reason, he could pass in the string "1,6,7" and it would actually validate as a numeric value. When passing in a fourth number, such as "1,6,7,9", it threw an error based on the argument type, which it rightfully should have done when being passed "1,6,7".

To try and narrow down the reason behind this behavior I whipped up a quickie function to test with. Here's the code:

<cffunction name="test" access="public" output="false" returntype="any">
 <cfargument name="thevalue" type="numeric" required="yes" />
 <cfset var retval = structnew() />
 <cfset retval.isnumeric = isnumeric(arguments.thevalue) />
 <cfset retval.rawvalue = arguments.thevalue />
 <cfset retval.dothemath = arguments.thevalue - 0 />
 <cfset retval.theval = val(arguments.thevalue) />
 <cfreturn retval />
</cffunction>

<cfset valList = "1,1,6|0,1|1,1,6,9|0,0,1" />

<cfloop list="#valList#" index="i" delimiters="|">
 Passing in: <cfoutput>#i#</cfoutput><br>
 <cftry>
  <cfset retval = test(i) />
 <cfcatch type="any">
  <cfset retval = "ERROR: " & cfcatch.detail />
 </cfcatch>
 </cftry>
 <cfdump var="#retval#">
 <hr>
</cfloop>

Here's the results:

results of a string validating against a numeric argument type

I tried many more combinations of numbers as well, and couldn't see any common denominators nor figure out what might be going on under the covers. Just thought I'd share this in case anybody had some input that might explain such behavior.

Posted by dougboude at 3:40 PM | PRINT THIS POST! | Link | 2 comments
26 April 2009
Auto-Escaping Characters When Outputting JS Function Calls

I'm blogging this little snippet mostly so that I have a place to find it the next time I need it, but perhaps it'll come in handy for someone else as well.

I'm creating some Javascript function calls on the fly as I output some query results. One of the parameters in the JS function is the value of an item's title which may at times contain characters JS tends to barf on, such as the single quote: '

In order to automatically escape such characters as I create my JS call, I used CF's ReReplace function. This function utilized a regular expression in order to do a search and replace, so I just created a simple regex that contained a list of all the characters i wanted to be automatically escaped.

Snippet time.

The specific code that performs the replacement:

ReReplace(Ucase(videotitle),"(['|.|;|?])","\\\1","all")

Distilled version of the output code:

<cfoutput query="qryVideos">

<a href="##" onclick="playVideo('#videoID#','#ReReplace(Ucase(videotitle),"(['|.|;|?])","\\\1","all")#','#vidPathRoot#/#videoPath#');return false;">watch this video</a>

<br>

</cfoutput>

Posted by dougboude at 6:49 PM | PRINT THIS POST! | Link | 4 comments
08 October 2008
Is Your ColdFusion User Group Lame?

For the last several months it has been my distinct opinion that the ColdFusion user group down here in San Antonio is, to put it frankly, lame. Attendance is high when we have more than three people show up (including the manager and co manager), the agenda is non-existent really, and all of our best meetings are held at the Flying Saucer (local college hangout with hundreds of kinds of beer) or TGIs (killer apple tinis). It's important that you understand I am not casting blame or pointing fingers...I believe our manager really does care and puts forth the effort needed. Nevertheless, this has been my personal assessment.

Last night was our September meeting. The manager called me and asked me to make sure and be there early because he was stuck in jury duty and wasn't sure when he would be out. So, I arrived at our regular meeting place (a training facility) only to find the room we always use occupied by a class. The sign that's usually taped to the door pointing the way to the meeting room wasn't there either. So I hung out in the lobby and waited for any other members to show up so we could discuss options. Eventually two guys walked in looking for the user group meeting. They were new faces, had never been to one of our meetings, and were interested in the topic of Flex that our manager had sent out as something we were going to talk about. I explained the situation to them, and for the next 45 minutes had an incredibly deep conversation revolving around Action Script 3 and Flash development, the forte of both of these guys. Our manager arrived soon after and we all went to the Flying Saucer where we spent the next two and a half hours geeking out (I say that affectionately), discussing specifics about projects we were working on, experiences we've had, and our own technical autobiographies. Some good connections were made on several levels, and I know that something positive will come from having gotten to know these guys over a few barley wines.

The real purpose of this post comes now and has to do with an epiphany I had on the way home. I obviously have been of the opinion that my user group is lame, most of that stemming from the fact that NOBODY hardly ever shows up for it. Up until last night, somewhere inside of me I harbored a kind of grudge against those others in this city who I KNOW use Coldfusion on a daily basis yet do not bother to show up and take part in the improvement of our craft as a whole. When I would try and understand why they so very often choose not to be there, I would always think "it must be because they don't get anything from our meetings", and would put my mind to coming up with ways to make the meetings more useful in a practical sense. Last night, though...it was a very small group, 5 in total (another guy joined us later after I called and invited him); yet the meeting was SO GOOD! And no, it wasn't just the barley wine. It was the dynamic of the conversations...I was LEARNING from these guys, I was being sharpened and improved simply by partaking of their experiences and sharing, and I was contributing like things from my own areas of expertise. So, boys and girls, here are my thoughts post-epiphany on the subject of user group meetings now...

If the reason you don't attend your user group meeting is because you don't think you're going to get anything from it...then shame, shame on you. The audacity one must have to believe that the user group is there solely for his or her benefit and partaking! Au contraire mon frere, au contraire. The user group is only as good as its members, bottom line. If you want it to be useful to you, then contribute to it in the form of BEING there and SHARING what you have learned! It's the manifestation of the old saying, you reap what you sow. If you want to gain something from the user group, then man, GIVE something to the user group! As I shared, our meeting last night was so inspiring and enlightening, opening my mind up to things I've not yet exposed it to and showing me the possibilities in these other avenues; but it was not solely because I sat idly by and just listened...I participated.  Your user group will only ever be as good as the members who support it, so if you, like I have done for so long, believe your user group is lame, then stop pointing fingers or casting blame! Attend the meetings and make them what you want them to be. Offer to give presentations, offer feedback and ideas to your managers, be outspoken and a participant rather than a spectator. In a nutshell, either do your part or stop complaining (even if its only to yourself).

And to those of you who DO attend your meetings but tend to worry over the fact that many others do not: stop worrying about it. Simple truth is, you probably are better off without the dead weight anyway.

Just my take.

Doug out.

Posted by dougboude at 2:16 PM | PRINT THIS POST! | Link | 6 comments
01 May 2008
Why I Hate ORMs (a solicited rant)
A necessary disclaimer...
Everything in the following post that appears to be an opinion most likely is and should be taken as such. My personal view on a topic does not (necessarily) invalidate any other opposing view. Readers should ascribe whatever value they so choose to the information that follows and either adopt it or reject it at their own discretion.

In the comment thread to a post I did on custom validation in Model Glue, I shared the fact that ORMs aren't my friends. In so doing, it prompted another commenter to ask me to share more details about my decision to be anti-ORM, as they too are weighing out the pros and cons of incorporating it:

"  Doug,
Can you elaborate on why you've abandoned ORM? I browsed thru your blog but didn't see any references to this decision. I'm wrestling with its costs vs benefits as well and would be interested in hearing from someone further down that path.

Thanks,
Dave  "


What follows are those details.

Why I am Anti-ORM


The Beginning

Just over a year ago, my team and I embarked on a journey to learn the ways of the object oriented programmer. We evaluated some of the popular Coldfusion MVC frameworks and decided to go with Model Glue. At the time, the trend was also to utilize other frameworks alongside Model Glue, so we too jumped on the bandwagon, reasoning that if we were going to adopt the tried and true standards of the OO world, we were going to embrace it completely. So, we architected our first MVC application, opting to utilize MG as our MVC framework, Coldspring as our IOC framework, and [brand X] as our ORM. For those of you who may not know what each of these frameworks are for, ModelGlue is the framework that separates, organizes, and "glues" together your views, your CFCs, and the controller CFCs that act as liaison between the other two. Coldspring is the IOC (Inversion of Control) framework whose job it is to manage the relationships between your CFCs. For instance, you might have a User.cfc that needs to perform a function located within another CFC such as Security.cfc. Using Coldspring, you can define that relationship ahead of time and, rather than hard coding the instantiation of your Security.cfc within your User.cfc, Coldspring will do that for you automatically whenever your app asks for the User.cfc. Ah, and now for [brand X]. {brand X] is an ORM framework whose job it is (and here's where my opinion and personal understanding heavily apply) to add a layer between your app and your database. The reason for this, you might ask? The reasons that my team and I saw for doing so were:

1) To allow us to easily change backend database platforms later down the road, should the need occur (not likely, right?)

2) To allow us to take advantage of some of the nifty "helper" features, such as auto-validation and scaffolding (automatically building Create, Update, View, and Delete forms for a given table)

3) To allow us to pre-define relationships between data so that we could "drill down" into child data by calling auto-generated methods, without having to query for it

4) Because using [brand X] was what every CFer "in the know" was doing, so it must be the right thing to do!

Sound like decent reasons, right?

I'll summarize, from my experience, the reasons that ORMs gave me to despise them. I'll try my best not to rant (actually I already did a lot of that in this other post).


The Reasons

The first reason my ORM gave me to hate it was that it forced me to have to synchronize case between field names and elements within different XML files. Coldfusion has LONG been a case-insensitive language for the most part, but suddenly now, as I am embarking on a journey through the foreignness of ORMs, I have to ALSO be mindful that everytime I type in a field name, I had just BETTER make sure I type it exactly the same way every time! Of course, I learned about this the hard way, through many hours of troubleshooting, following nearly useless error messages, and piecing together scraps and tidbits from here and there on the net building myself a "Franken-Solution".

Second reason: the Coldfusion ORM frameworks are a work in progress, and thus subject to undiscovered anomolies coded into their core. While my team and I were burning the midnight oil trying to learn and use our ORM, it kept changing on us. We'd update and replace core files only to find that some behaviors had suddenly changed. The one thing that DIDN'T change were the nearly useless error messages, though...that kept it "interesting" for us.

Hey, this ever happen to you? You're building an application and suddenly realize you need another field in a certain table, so you...go ahead and add it? Sheesh, it happens ten times a day sometimes! A very common occurrence, a very expected occurrence, right? Which leads me to the third reason my ORM gave me to hate it: our ORM didn't take kindly to change. It took us FAR too long to try and figure out how to cause it to see and propagate database changes throughout the app without spilling its groceries into a useless error message. The final solution, that took us weeks of evolving to: nuke it. Just delete the whole cotton-picking cache of files that our ORM had automagically generated for us and FORCE it to recreate them all. This approach defies common sense, and thus it was the end of our evolution and not the beginning. Why's an ORM gotta defy common sense?

The fourth reason: gluttony. As mentioned in the list of reasons why we chose to use an ORM, we wanted to leverage the beauty of pre-defining data relationships and be able to drill down into child data with ease. In theory, this is a beautiful thing. In practice, it's a disgusting hag. The bloated, inefficient objects within objects within objects that get created in this process can cause an app to literally CRAWL. What would take less than a second to perform the traditional way can take many hundreds of times that amount of time when using these auto-generated object nests. When you do take the time to define all of these relationships as they truly are (and good luck getting that right the first five tries), the resulting objects are ginormous and slow as molasses on a cold January morning. Painfully slow. Not acceptable, ORM. Not acceptable.

And ah, the grandest reason of them all that I pretty much despise ORMs: losing my beloved sql. Now, I wouldn't have a problem with losing sql as long as my ORM provides me with a suitable substitute. But it does not. The task of translating a simple sql statement into "ORM-speak" is far, far from simple, my friend. I and others I know have quite literally spent an entire DAY trying to figure out how to write the code, leveraging our ORM, to execute a simple join. Don't get me wrong, it IS do-able. But from the perspective of someone who knows how to query a database, doing the same thing using only ORM objects is convoluted, WAY over-complicated, gluttonous from an efficiency point of view, time consuming, and in the end....what the heck good is it anyway? What did I just do to help myself by spending an entire DAY figuring out how to define object relationships in a way that my ORM likes, defining events that have every parameter present and properly cased, making sure that the database changes I make along the way are actually recognized by my ORM and my application has been properly reinitialized, and then synchronizing the whole thing and crossing my fingers that it'll actually work and not throw me back some useless error? Writing and executing what was and is simple SQL is ridiculously convoluted by an ORM, eats up a huge amount of precious development hours in figuring out how to do it, and gives me only negative return on that investment by creating code that increases my bottom line in IO and makes it so that only another person who has drunk the ORM kool-aid can possibly ever understand. Oh yeah, and when you DO finally figure out how to write a simple join, you then realize that there are actually probably four or five different ways you could have done it using the ORM objects; which one is most efficient? who the heck knows, and you'll only know if you invest the hours to write it with each approach and time it yourself. And even your ORM's BEST and most efficient approach will STILL NOT beat the time you would get by executing straight SQL...there's no way.


Conclusion

What this and any decision comes down to is really just a list of reasons TO do something versus reasons NOT to do it. In this case, based on what I consider to be thorough personal experience combined with that of my peers, adopting an ORM has an extremely sparse list of "Pros" which in no way even come CLOSE to outweighing the list of Cons that come with it.

I realize I didn't do very well at filtering out my ranting. I also realize that, if you happen to be a person who has already gone to ORM prison and now you're used to the lifestyle of having it dwell within your apps, you're going to have a totally different take on it. But I'd be willing to bet money that even you die-hard ORMians felt the same way I do at one point in your J-Curve, only instead of ceasing to knock your head against the wall, you banged a little longer and finally broke through it. Now it's second nature to you to  know where NOT to step in order to avoid the landmines, so you can play soccer freely in the minefield having only lost a few virtual limbs in the process.  HOWEVER, if you are the person who has yet to step into that tempting and beckoning ORM minefield, if you're still on this side of the kool-aid, you should know that forcing yourself to use and learn an ORM is a bit like forcing yourself to learn to smoke. Yeah, given enough choking, hacking, and puffing, you CAN eventually learn to love the feeling of hot, acrid, killer gases in your lungs; but is the miniscule physical pleasure and social "coolness" worth the initial pain, suffering, and certain long term problems that go with it? I say no way, and after having hacked and puffed on my chosen ORM for a solid six months, I say no way to that as well.

I am of course committing a social faux pas by pre-judging all ORMs based on my experience with one, much the same way as it is commonly said that you can't judge all women by the way one woman treated you. But you know what? Even the slickest ORM is STILL only going to provide me with 2 or 3 pros, tops, and will STILL of necessity be adding overhead to my application's efficiency and learning curve to my timeline. From that perspective then, yes, I AM pre judging and have every intention of remaining "ORM Celibate" from here on out. I've worked on several applications since the one I mentioned earlier, all completely without an ORM involved, and have been utterly delighted with their performance. Good riddance, ORMs, I'm not missing you at all and will probably never recommend you.

Doug out.
Posted by dougboude at 11:54 AM | PRINT THIS POST! | Link | 20 comments
09 March 2008
Client-Side Drilldowns Made Easy
Last September I shared a post on an alternative to Ajax for client-side interactivity leveraging Coldfusion's WDDX. I'd like to take it a step further now and share an approach (and corresponding code) I often use in my Model-Glue apps when needing to create tiered or drilldown-type select lists withOUT having to make numerous calls to the server. The gist of this methodology is the same as in my previous post:
  1. Retrieve all needed data sets for populating the drilldowns;
  2. Convert those CF queries to a form that Javascript can manipulate;
  3. Write the necessary functions to populate the dropdowns based on previous options selected.

Here is the working sample. Go ahead, play with it!





The main difference in this example is the fact that I'm not utilizing WDDX, but rather JSON to make the data Javascript friendly.

Let me show you what the queries look like for each tier of this particular drilldown:
Tier 1Tier 2Tier 3

An important item of note here is that the data set for a given tier must include the parent ID of the previous tier in order to perform the filtering operations you'll see soon.

Okay, so how to get the queries into a format Javascript can manipulate. The route I chose to go was to convert the queries to JSON via one of two methods, depending on what version of CF you're using. Since not everyone is using version 8 yet, I made this example compatible with anything 6 or higher (versions previous to 8 are dependent on outside conversion; I chose to use JSON.CFC. Version 8 can utilize the built in function "serializeJSON"). Here's the statement where the queries are transformed (using the commonly known custom tag "QuerySim" to create the data for this example):

<cf_querysim>
    level_1
    level_1_id,name
    1|Colors
    2|Shapes
    3|Foods
</cf_querysim>
<cf_querysim>
    level_2
    level_2_id,level_1_id,name
    1|1|Red
    2|1|Blue
    3|1|Yellow
    4|2|Triangle
    5|2|Square
    6|2|Circle
    7|3|Bread
    8|3|Meat
    9|3|Fruit
</cf_querysim>
<cf_querysim>
    level_3
    level_3_id,level_2_id,name
    1|1|Fuschia
    2|1|Brick Red
    3|1|InfraRed
    4|2|Teal
    5|2|Cyan
    6|2|Navy Blue
    7|3|Light Yellow
    8|3|Dark Yellow
    9|4|Isosceles
    10|4|Equilateral
    11|4|Right Triangle
    12|5|Rectangle
    13|5|Parallelogram
    14|6|Ellipse
    15|6|Oval
    16|7|Matzah
    17|7|Hot Cross Buns
    18|7|Brioche
    19|8|Steak
    20|8|Fajitas
    21|8|Hamburger
    22|9|Kiwi
    23|9|Grapes
    24|9|Oranges
</cf_querysim>

<!--- convert our query data to JSON strings...mind the second parameter to the serializeJSON function... --->

<cfif listfirst(SERVER.ColdFusion.ProductVersion) gt 7>
    <cfset level_2_json = serializeJSON(level_2, true) />
    <cfset level_3_json = serializeJSON(level_3, true) />
<cfelse><!--- we're on less than version 8. use json.cfc --->
    <cfset objJSON = createobject("component","json") />
    <cfset level_2_json = replace(objJSON.encode(level_2),"data","DATA","all") />
    <cfset level_3_json = replace(objJSON.encode(level_3),"data","DATA","all") />
</cfif>

<script>
    //set our json data into Javascript objects
    var lev2data = <cfoutput>#level_2_json#</cfoutput>;
    var lev3data = <cfoutput>#level_3_json#</cfoutput>;
</script>

You'll notice in the section where I rely on JSON.CFC, I am doing a replace on the lower case word "data" to make it upper case. This is to make the JSON string produced consistent with the one produced utilizing serializeJSON. Since Javascript is case sensitive, case consistency in the JSON is required if you wish to utilize only one javascript function to perform the select list population. You'll also notice the use of the optional secondary parameter in the serializeJSON function call. This is needed in order to produce a JSON string that can be accessed by Javascript exactly the same as the JSON.CFC string.

MG NOTE: Regarding the fact that I utilize this approach in Model-Glue apps...I have my controller return an already formatted JSON string to my view rather than return a query and then perform the transformation there. Many of my controller methods have an optional "returnJSON" argument that I use when I need a JSON string back rather than a query.

Okay, data sets are available to Javascript. Now to write a few Javascript functions that can spin through that data and populate the appropriate select list. Here are the functions needed to perform the necessary tasks:

<script>
    //function to repopulate targeted select list
    function repopulate(targetObjID,targetDataSet,selectedIDVal, idColName, valColName, optionValColName){
        /*
         parameters:
         targetObjID - the ID of the select list we want to populate;
         targetDataSet - the actual Javascript data object we created previously;
         selectedIDVal - the name of the column in this data set that contains the parent record ID
         idColName - the name of the column that contains THIS tier's own record ID
         valColName - the name of the column that contains the data we want to display as the option text in the dropdown;
         optionValColName - the name of the column that contains the value we want to use as the new options's VALUE value;
        */
       
        //loop over the data object. for every object with a keyname of idval, add it to the dropdown
        var objTarget = document.getElementById(targetObjID);
        ResetObject(objTarget);
        for(i=0;i<targetDataSet.DATA[idColName].length;i++){
            if(targetDataSet.DATA[idColName][i] == selectedIDVal){
                objTarget[objTarget.options.length] = new Option(targetDataSet.DATA[valColName][i].substring(0,45),targetDataSet.DATA[optionValColName][i],false,false);
            }
        }
    }
    //function to clear a dropdown
    function ResetObject(objTarget){
            objTarget.options.length=0;
            objTarget.options[0] = new Option("---------------","",false,false);
            return;
    }

function resetAll(objIDList){//empty out all of the dropdowns specified in objIDList
        var idlist = objIDList.split(",");
        for (i=0;i<idlist.length;i++){
                ResetObject(document.getElementById(idlist[i]));
        }
        return;
}
    
</script>


Of note is the fact that we are working with three select lists. The first select list is always populated at load time and needs no Javascript intervention at all. Select lists "level_2" and "level_3", however, are being completely manipulated by the JS calls by one generically written function.

Last but not least, the HTML with the Javascript calls embedded in the select lists' onChange event:

<body>
    <h2>Client-Side Drilldown Example</h2>
    Level 1: <select name="level_1" id="level_1" onChange="resetAll('level_2,level_3');repopulate('level_2',lev2data,this.options[this.selectedIndex].value,'level_1_id','name','level_2_id');">
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
        <cfoutput query="level_1">
            <option value="#level_1_id#">#name#</option>
        </cfoutput>
    </select>
    <hr width="25%" align="left">
    Level 2:
    <select name="level_2" id="level_2" onChange="resetAll('level_3');repopulate('level_3',lev3data,this.options[this.selectedIndex].value,'level_2_id','name','level_3_id');">
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
    </select>
    <input type="button" value="show ID" onClick="alert(document.getElementById('level_2').options[document.getElementById('level_2').selectedIndex].value);">
    <hr width="25%" align="left">
    Level 3:
    <select name="level_3" id="level_3" >
        <option value=""><cfoutput>#repeatstring("-",15)#</cfoutput></option>
    </select>
    <input type="button" value="show ID" onClick="alert(document.getElementById('level_3').options[document.getElementById('level_3').selectedIndex].value);">
</body>

Voila! That's it! instant, client-side drilldown with only a single call to the server!

Hope it saves someone a little time. ;)

Doug out.
Posted by dougboude at 10:46 PM | PRINT THIS POST! | Link | 0 comments
07 March 2008
For Your Entertainment Only: CF is Dead Dialogue
The following post is for entertainment purposes only and is not intended to prompt any real responses from anyone. In addition, if you're truly tired of the topic of "Coldfusion is Dead", don't even waste your time reading this post. I just received a comment from a Javaphyte on an older post I had done and couldn't resist digging him a little bit, so thought I'd share it.

Javaphyte's Comment:

Re: COLDFUSION IS DEAD! So says Dave Lowe
All coldfusion is now is an oldschool scripting language being interpreted by a Java server. If you are one of those unlucky companies that wasted your money on a Coldfusion server, hire Java developers and move away from CF to Java, because you can program in Java on a CF server and it makes more sense to do so since Java is more powerful. Sure you can go the highschool programmer way and use a CFScript tag and put cheesy javascriptized java in it, but that's just asinine.
Posted by rey on March 5, 2008 at 7:40 PM

My Comment:
Re: COLDFUSION IS DEAD! So says Dave Lowe
Ah, spoken like a true Javaphyte! Perhaps there IS some hint of validity in saying that because a language is more cryptic,requires years of schooling, and more actual lines of code to accomplish a task, that it is more powerful. Or IS there validity in that observation???? Perhaps, too, the Pony Express was a more "powerful" means of disseminating the mail than is, say, email, because it required such extreme logistical prowess and sheer number of bodies to accomplish its mission? The truth is, the people who cling to the myth that their language is "more powerful" are the same kind of people who, with no valid practical reason, buy large pickup trucks: they're trying to compensate for some deep-seated, behind the scenes insecurity they harbor about themselves as a person. Because in Java I am forced to write many lines of code to connect to a database and retrieve a data set (and let's not talk about how many lines of code I might need to iterate OVER that data and actually do something with it) while in a scripted language like, say, Coldfusion, I can do it in one line, does that make Java "more powerful"??? HECK no, especially when my "dead language" is simply tapping the interfaces Java already provides me while simultaneously allowing me, if I wish, to execute Java code directly. Ah, and let's not forget the fact that in many arenas, the Javaphytes have THEMSELVES adopted the way of the "high school programmer" and have created for themselves...what? TAG LIBRARIES! Yay! Why? Because some of the Javaphytes became enlightened and began packaging up certain of Java's functions in simple to read, simple to type, simple to use...tags. And what is Coldfusion, my Javaphyte friends? It is an extremely robust tag library for Java. Now isn't that the pot calling the kettle black.

Besides being of the firm opinion that the world, when taken as a whole, is mildly retarded, I am also of the firm opinion that there are those developers out there who directly derive their own sense of self-worth from how overly complicated and complex they can make a simple task. After all, if I devise a system of pullies, ropes, and ramps to move my chair across the room, aren't I smarter than the person who simply rolls it over there by hand? In case you didn't figure it out, the answer is "No, you're not smarter than the person who just rolled it over there".

Anyway, thanks the entertaining peek into the mind of a Javaphyte, Rey! :) Oh, and be sure and avoid using any Java tag libraries...someone might see you.
Posted by dougboude at 2:40 AM | PRINT THIS POST! | Link | 6 comments
06 February 2008
JSON.CFC, MAYBE CF8 DIDN'T MAKE YOU OBSOLETE AFTER ALL!

Back in July I shared the results of my personal forray into JSON as a blog post, comparing it to XML as a means of returning Ajax data. In order to conduct my experiments, I had utilized an open source CFC called JSON.CFC in order to do the conversion from CF data types to JSON and vica versa. After the post, one individual made the comment that once CF 8 came out, there wouldn't be a need for JSON.CFC anymore. Well, until today, I would have agreed, but now I'm not so sure.

I'm working on a project where I want to retrieve some data sets and keep them client side for fast lookups, so my approach was to return the data to my template as a JSON string, and then just 'pop' it into Javascript, like so:

<script>
 var seriesgroups = <cfoutput>#seriesgroups#</cfoutput>;
</script>

FYI, the my data has these fields:  SERIESGROUPID,NAME

What I expected was that, once I did this, I would be able to access my values in JS like this:

<input type="button" value="test" onClick="alert(seriesgroups.data.name[0]);" />

...


My data was converted from a query to JSON using CF8's new "serializeJSON()" function. But to my mild irritation, I was not able to access the data as expected. I thought to myself, "self, this is an odd thing, because when I performed the same task using the JSON component a few months ago, it worked fine". Just to double check myself, I re-read my blog post on JSON and decided to use JSON.CFC to see if the results were the same. Using JSON.CFC, I was able to get the expected results.

So here's the thing, then: JSON.CFC, MAYBE YOU'RE NOT SO OBSOLETE AFTER ALL!

Or, it could also be that I am just not adept enough at reading JSON strings and can't figure out the appropriate way to access my JS object in such a way that I could easily loop over it looking for specific values. If anyone knows how one SHOULD go about accessing data that was serialized using "serializeJSON" in a JS context, I would love to see an example! I could probably figure it out if I had the luxury of time, but alas, I do not.

Okay, just to top things off with a side by side comparison (which I love doing), here are the results of the CF 8 and JSON.CFC conversions:

Method JSON String
JSON.CFC {"recordcount":1,"columnlist":"name,seriesgroupid","data":{"name":["Exports"],"seriesgroupid":[1]}}
serializeJSON() {"COLUMNS":["SERIESGROUPID","NAME"],"DATA":[[1,"Exports"]]}


JSON.CFC dump serializeJSON dump
dump of cfjson results dump of the serializeJSON results
Posted by dougboude at 4:20 PM | PRINT THIS POST! | Link | 8 comments
04 February 2008
Using Google as your CF Mail Server
Ah, sounds like a no brainer, right? Why even do a blog post on such a thing, right? Because Google requires that an SMTP connection be encrypted...something that CFMX7 just doesn't do all by its lonesome. I need to vent a little, so here's my story followed by some useful info....

It's extremely late on a Sunday night and you're in the final final stages of a site migration for a client. Their "under construction" sign says that everything will be back online and kosher at 7 am Monday morning, so you're addressing one of the last things on your list: setting up the client's email hosting server.

It made it to the end of the list because of the fact that they're using a third party to relay their mail: Google. Now everybody knows Google's reputation for being logical and actually making sense most of the time, so how hard can it be to make sure that Coldfusion is pointing to the client's Google SMTP, especially when the client said it was a piece of cake when they set it up the first time? You figure out later that the person who told you that wasn't actually the person who set it up to begin with, as the passing time turns into a couple of hours and you still haven't sent a successful email.

(Here comes the venting part I mentioned...) You know what chaps my hide, grinds my gears, gets my goat (I wonder where that one evolved from), et al? It's the same thing that gets my goat most of the time, the incredibly cryptic nature of some things that ought to be simple and all of the other people out there on the net who managed to get it to work but insist on only sharing yet more cryptic or worse yet, elusive, information on how they did it (if they share any of the "hows" at all!). I want to share with you my journey, at 2 am on a Sunday night, to learning how to set up a secure tunnel to an smtp server for the purpose of using Google's smtp server for sending email. I want to do this, ALL IN ONE BLOG POST, just in case anybody else out there EVER finds themselves in the position I was...I wouldn't wish such a frustrating, elusive game of Clue on any other person.

CUTTIN' TO THE CHASE, if you're trying to use Google's smtp server to send mail and you're seeing errors like this one
coldfusion email error log

in your mail log file, it's telling you that Google requires you to authenticate to its smtp server via an SSL connection, and any Coldfusion version below 8 (I read a short post saying that version 8 could handle such things, but the poster FAILED to give any other clues as to how to implement it...sheesh) can't do that all by its lonesome. So, we need an intercessor, a little service running on the web server that will intercept requests to ports internally then reroute the request to an outward-facing port using a secure connection. That intercessor is called 'STunnel' (though there were a couple of other equal products I came across). In a numbered list then, here are the steps a body needs to do in order to make it work:

1. Make sure you have a Google email account.
My client is signed up to use Google Apps, which includes an email server. Here's a starter link for you: https://www.google.com/a/help/intl/en/admins/editions.html

2. Make sure you know the username and password for the Google email account

3. Download and install a program called STUNNEL (latest windows exe can be found on this page: http://www.stunnel.org/download/binaries.html)

4. Edit Stunnel's configuration file (the default config file is in the stunnel directory, and is called "stunnel.conf").

5. Empty out the config file and paste  in the following values:
client = yes
debug = debug
[pop3s]
accept = 127.0.0.1:1109
connect = pop.gmail.com:995

[smtps]
accept = 127.0.0.1:259
connect = smtp.gmail.com:465


6. Go into your CF Administrator, to the mail settings, and enter the appropriate values according to the illustration and your own username and password

coldfusion mail server settings

(note: 127.0.0.1 is not a placeholder value in this blog post: use that value! Also, notice the port number used, 259; it's the same number in the stunnel.conf file for accepting SMTP mail.)

7. Go to the stunnel directory under Programs and choose to install it as a service, then run it as a service.

8. Go to your services list and make sure stunnel is running.

9. Just for fun, restart the CF service

10. Execute a template you made that has a cfmail tag set up similar to the following:

    <CFMAIL
            TO="dougboude@gmail.com"
            FROM="admin@myclient.com"
            SUBJECT="test email from server"
            TYPE="HTML">
    testing
    </CFMAIL>

11. Log in to your google mail and look in your sent items folder. The email you sent should appear there tout suite. (Google apps allows you to point a subdomain to their email server...my client's is similar to "mailbox.clientdomain.com", fyi)

12. if you don't see the email in the sent items folder, check CF's mail log and make sure it didn't generate a STARTTLS error

If you do all of this and it still doesn't work, I'm afraid I have no more information for you. BUT, if you run into additional issues and DO manage to fix them, please post that info back to the comments area of this post for the benefit of others!

Okay, I feel better now. Being forced to invest SO much time and energy, at 2 am, and then finding nothing but a few nearly useless clues here and there to go on can really drain a person emotionally, ya know? :)

Doug out.
Posted by dougboude at 12:13 PM | PRINT THIS POST! | Link | 12 comments
08 October 2007
Appropriate Usage of the "THIS" Scope
For the past several days, my good friend Jim and I have been having a series of discussions regarding the use of the 'THIS' scope within CFCs; specifically, CFCs that are to be used in an OO fashion. He's part of a small team that has just begun their OO journey and are embarking on a project where they hope to grow and refine their understanding in this arena. Jim being the stickler for correctness that he is, he often bounces approaches and ideas off of me to see what my take is, as well as compare the code he produces to that of his fellow teammates. Here is where a certain rift has manifested itself among them, as his fellow coders are producing CFCs that make heavy use of the 'THIS' scope and little to no use of 'Variables' and 'Var', while Jim is not using 'THIS' at all and is rather declaring his variables as public within the 'Variables' scope or private using 'Var'.

My personal take on the use of 'THIS' within a CFC (and what I firmly expressed to Jim) is that it would be a rare circumstance when I would see 'THIS' as the appropriate scope, since putting variables into 'THIS' exposes them to actions performed outside of the CFC. Just visualizing a CFC with all it's internal variables in the 'THIS' scope immediately causes me to think, "You've just reduced your object down to a very primitive state...a Transfer Object. Why don't you just rather create a structure to hold those values since you're ACCESSING them as if your object WERE a structure." Basically, I've been telling Jim that using 'THIS', except in very specific circumstances, is a bad thing to do.

His own intuition and experience up to this point tells him the same thing, since his comprehension of 'Variables' and 'Var' is quite clear. He has as of yet, however, been unable to convince his peers of this, and has even been admonished to STOP using 'Variables' and 'Var' and start putting his CFC variables in 'THIS'.

Because Jim is a stickler for learning to do things the "right" way, he set out on a Google journey to find documented proof that what he and I believe regarding 'THIS' is absolutely correct. His search has left him empty-handed, though, and so he and I turn to you, our fellow developers, for input on this topic.

The question then: What are your basic rules of thumb regarding the use of 'THIS' when writing a CFC to be used in an OO application (even a loosely structured one)?

Thanks in advance for sharing your opinion!

Doug  :0)
Posted by dougboude at 11:45 AM | PRINT THIS POST! | Link | 6 comments
04 October 2007
Yet Another Model-Glue Quickstart...
Jim Pickering, the manager of the Kansas City CF User group, recently started a group project to rebuild their UG's web site. The project has the secondary agenda of promoting team development and learning Model-Glue, so he invited me to participate by setting up a skeleton app with basic security and then giving his volunteers a "Model-Glue Quickstart".  The entire presentation is just over an hour long, and includes:

  • Jim giving an overview of setting up the local development environment (minutes 0 through 2),
  • a Model-Glue overview and dissection of the basic security functionality (minutes 2 through 34),
  • and the adding of a new piece of functionality to the app (minutes 34 through the end).

There are places where the visuals and the dialog got a bit out of whack (minutes 15 or so and onward), but it levels out enough to not be irritating around minute 25.

So if you're interested in yet another high speed "Model-Glue for The Rest of Us" style preso, here it be!

https://admin.adobe.acrobat.com/_a200985228/p70824254/
Posted by dougboude at 12:51 PM | PRINT THIS POST! | Link | 8 comments
03 October 2007
Model-Glue XML Isn't Valid!!! Short Reminder
Don't you hate it when you're focused on adding functionality and/or troubleshooting issues in your Model-Glue app and when you save your modelglue.xml file to see if it all works right, you get the error:

Model-Glue: C:\ColdFusion8\wwwroot\yourAppName\config\modelglue.xml isn't valid XML!

I hate it. A lot.

See if you can spot the earth-shattering error in the following snippet of trouble-causing xml:


<event-handler name="view.register">
    <broadcasts />
    <results>
        <result do="view.template" />
    </results>
    <views>
        <include name="body" template="dsp_register.cfm" />
    </Views>
</event-handler>


This has been my day to be mentally challenged for some reason, so perhaps all of you found it quickly. But fifteen minutes, several experiments to isolate the offending code block, and a visit to an online XML validator later, I was reminded that XML is case sensitive. The way my font settings are in my IDE make a lower case and capital "V" look VERY much alike, so I kept looking right over it.

Just thought I'd share this in case it helps someone else save a few minutes. ;)

Posted by dougboude at 4:36 PM | PRINT THIS POST! | Link | 2 comments
02 October 2007
And Now For Something Completely Different...

A sign I made for the bathroom stall at work...I'll let you guess why I felt it necessary.

Feel free to use it, too!


The Cat in the Hat
If you’re sitting on the potty cause your side’s about to break

And your colon it’s a rollin’ and your nether regions ache;

If you’re droppin’ logs like Enron stock with every single push,

Then please think of your fellow man and give a double flush!
Horton the Elephant
Posted by dougboude at 9:17 PM | PRINT THIS POST! | Link | 2 comments
Dumping An Object is Like Taking an X-Ray
an OOP noobie analogy
Lately I've had the privilege of helping a good friend of mine climb "Mt. OOP", and in the process have been able to refine a lot of my own knowledge. I've also discovered that I sometimes make too many assumptions when imparting understandings, and this short post is in regards to one of those items: Dumping Objects.

One thing I always urge my fellow developers to do is to form a solid expectation of what the results will be when the code they're writing is executed.  Well, what I found out is that my friend (and the rest of his team who are just getting their feet wet using objects) were certain that dumping an object should allow them to see EVERYTHING inside of it. Consider the following CFC:

<CFCOMPONENT DISPLAYNAME="FleshAndBones">
    <CFFUNCTION NAME="init" ACCESS="public" RETURNTYPE="any" hint="I return the body with a single organ">
        <CFARGUMENT NAME="initialOrgan" TYPE="string" REQUIRED="yes" hint="I am the first organ to be added to the body">
        <CFSET variables.stBody = structNew() />
        <cfset variables.stBody.organ1 = arguments.initialOrgan />
        <CFRETURN THIS />
    </CFFUNCTION>

    <CFFUNCTION ACCESS="public" NAME="addOrgan" OUTPUT="false" RETURNTYPE="void" hint="I add a new organ to the body!">
        <CFARGUMENT NAME="thisOrgan" TYPE="string" REQUIRED="yes" hint="I am the organ being added">
        <cfset var newKey = "organ" & structcount(variables.stBody) + 1 />
        <cfset variables.stBody[newKey] = arguments.thisOrgan />
    </CFFUNCTION>
</CFCOMPONENT>


Now, consider the following code which creates, initializes, populates, and dumps the "FleshAndBones" object:

<cfset objBod = createobject("component","FleshAndBones").init(initialOrgan="Heart") />
<cfset objBod.addOrgan("Lungs") />
<cfset objBod.addOrgan("Pancreas") />

<cfdump var="#objBod#" />

The expectation was that when dumping an instance of the objBod object, "variables.stBody" should be visible somewhere in the dump. When it wasn't, red flags were raised and a revisiting of the CFC occurred until, by golly, the developer FORCED that variable to show up in the dump! How?  By experimenting with scopes until he found one that allowed it to be visible: the THIS scope. Bad form, Jack. Putting variables into the THIS scope without a VERY good reason is tantamount to circumventing the very purpose of using an object, in my opinion. If you're going to do that you may as well just create a structure and manipulate that since the object will pretty much be behaving the same way. Enough on that, though.

Here is what the dump DOES contain:



To help explain what SHOULD be expected in the dump of an object, I gave my friend an analogy that helped him so much that he said I should definitely share it on my blog, so here it is:

CFDUMPing an object is exactly like taking an X-Ray of the object: You should expect ONLY to see the bones, not the soft innards. The bones of an object are its methods and metadata such as the method hints, return types, incoming arguments, etc. The soft innards are all of the variables, both private and public, that have been declared. It could also be any queries that have been executed, structures that were created, or anything else for that matter. Nothing besides the bones will ever be visible on an X-Ray, so not seeing those other internal items in the content of the dump is perfectly normal and is what SHOULD be expected.

Now, very often you WILL want to see what some of those soft innards of your object are looking like at various times. The answer to that is simply to make sure you have a method present whose job it is to return that particular soft innard, such as the following:

<CFFUNCTION ACCESS="public" NAME="getBody" OUTPUT="false" RETURNTYPE="struct" hint="I return the body!">
    <cfreturn variables.stBody />
</CFFUNCTION>


You can then dump that soft innard like so:

<cfdump var="#objBod.getBody()#" />




So in summary... CFDUMP (and the "getMetaData" function) give you X-Rays of an object. If you want to see the squishy parts, create a method that returns them!

Doug out.
Posted by dougboude at 3:03 PM | PRINT THIS POST! | Link | 0 comments
27 September 2007
Global Configuration in Model-Glue: Using Your Own Config CFC
In a post I did a while back, I shared what I had learned about using Model-Glue's built in "SimpleConfig" cfc. Well, I just got a question from a fellow developer who was trying to leverage the information in that post to implement his own home grown configuration settings cfc, and what I had shared there didn't quite apply. I tried to email him back, but it was returned as undeliverable, so I thought I'd just post my response to him here for the sake of anybody else who is endeavoring to do the same thing.

His email:

Hi Doug,

I love your post (http://www.dougboude.com/blog/1/2007/06/Global-Configuration-Settings-in-ModelGlueUnity.cfm)

I was actually finding it hard to implement though. I basically have written a settings.cfc with a medium  sized struct containing all my site settings. I have functions like getProperty([name]) which returns the required value.

Now, I am just getting into MG and i  am finding it hard to simply put this in the global scope available to my Models, Views and Controllers.

I read your article but am still confused.

Can you help us out with this?

Thanks



My response to...we'll call him Johnny:


Sure can, Johnny!  Since you have created your OWN settings CFC, which I have also done in other MG apps, here's what I completely recommend doing: use Coldspring. 

What we're going to do is let Coldspring handle the "injection" of our settings object into those other objects that need it. Let's say for an example that we have a USER.cfc in our model directory that will need the DSN in order to perform a query. The DSN value is located in our SETTINGS.cfc. So, in the Coldspring.XML file we're going to have to do two things:

1. tell Coldspring about our settings.cfc
2. tell Coldspring about our user.cfc, at the same time telling it to put 'settings' inside of 'user' 

Now, two questions become immediately apparent:

1. What is the proper syntax for telling Coldspring the above two items?
2. What do I have to do special inside of my USER.cfc in order to help Coldspring do what I told it to do?

Telling Coldspring About Your Objects

Look at the Coldspring.XML snippet below. It is accomplishing the first two items we said we needed to tell Coldspring about (our settings cfc and our user cfc):

<bean id="Settings" class="model.settings" />

<bean id="User" class="model.user" >
    <property name="Settings">
       <ref bean="Settings"/>
    </property>
</bean>


Accommodating Coldspring Within Our CFCs

Now, what do we do special inside our User.CFC in order to help Coldspring accomplish what we told it to do? We create two additional methods, one called SetSettings and one called GetSettings.

Here's what those two methods should look like:

<CFFUNCTION name="SetSettings" access="public" returntype="void" output="false">
       <CFARGUMENT name="Settings" type="model.settings" required="true" />
       <CFSET variables._Settings = arguments.Settings />
</CFFUNCTION>

<CFFUNCTION name="GetSettings" access="public"
returntype="model.settings" output="false">
       <CFRETURN variables._Settings />
</CFFUNCTION>


By us including those two methods (notice the correlation between the names of the methods...get[bean id here] and set[bean id here] to the ID value we chose in the Coldspring.XML snippet), Coldspring will inject an instance of your settings object. Then, within any method inside of the User.cfc, we can access our settings
with "GetSettings().getProperty([name])".

For clarification, here is a sample USER.cfc to illustrate what I mean:
<CFCOMPONENT DISPLAYNAME="user object" >
    <CFFUNCTION name="SetSettings" access="public" returntype="void" output="false">
           <CFARGUMENT name="Settings" type="model.settings" required="true" />
           <CFSET variables._Settings = arguments.Settings />
    </CFFUNCTION>
   
    <CFFUNCTION name="GetSettings" access="public" returntype="model.settings" output="false">
           <CFRETURN variables._Settings />
    </CFFUNCTION>
   
    <CFFUNCTION NAME="login" ACCESS="public" RETURNTYPE="string">
        <CFARGUMENT NAME="username" TYPE="string" REQUIRED="yes" />
        <CFARGUMENT NAME="password" TYPE="string" REQUIRED="yes" />
       
        <cfset var retval = "" />
       
        <CFQUERY NAME="LogMeIn" datasource="#GetSettings().getProperty("DSN")#">
            select * from users where
            username = <cfqueryparam value="#arguments.username#">
            AND
            password = <cfqueryparam value="#arguments.password#">
        </CFQUERY>
       
        <cfif LogMeIn.recordcount eq 1>
            <cfset retval = "success" />
        <cfelse>
            <cfset retval = "failure" />
        </cfif>
       
        <CFRETURN retval />
    </CFFUNCTION>
</CFCOMPONENT>
This sample assumes that your homegrown settings cfc uses a method called 'getProperty' to retrieve values


That's it!

For every CFC that requires an instance of your Settings.cfc, just make sure that you define it in Coldspring.XML AND include the Set and Get methods named appropriately inside of the recipient CFC.


A SPECIAL CASE

IF you are trying to inject your Settings.cfc into a Modelglue CONTROLLER cfc, then there's one less step involved for you. The controller CFC still needs the GET and SET methods present inside itself, BUT you do NOT need to define the controller bean within Coldspring.XML. Allow me to repeat myself: As long as the Settings bean is defined in Coldspring.XML, all you have to do to have that injected into a CONTROLLER cfc is to create a SET and GET method identical to the examples above within the recipient controller cfc.


In Closing

I'll close by saying that there are other ways, using Coldspring, to get a Settings object into other objects, but in my opinion those approaches are no better or worse than the one I described above, so let's just keep it simple and stick to this one.

Hope this helps, Johnny, and let me know if you have any more questions; I'm always glad to share what I've learned.

Doug  :0)
Posted by dougboude at 3:21 PM | PRINT THIS POST! | Link | 0 comments
25 September 2007
My Approach to Basic Security in a Model-Glue Application
By now almost all of us have "rolled our own" when it comes to giving a Coldfusion app some semblance of security, so the concept is nothing new. It can be, however, somewhat daunting when attempting to take what we know procedurally and apply it to an MVC styled application.

The Kansas City Coldfusion User Group invited me to assist them with a group project using Model-Glue, specifically asking me to show them how to implement security. What follows is my way of thinking about and approaching the implementation of basic security in a Model-Glue application. At the end of this post is a link to download the tutorial app I put together for them as well since I know "a line of code replaces a thousand lines of abstract explanations".

GOALS OF SECURITY

What we’re wanting to accomplish is this:
  1. User arrives at our app.
  2. We evaluate their current login status, setting the appropriate values in the appropriate places so that those portions of our app who DO care if the user is logged in or not can make the right choices.
  3. Those values follow the user along their tour of our app.
     

PRE-REQUEST ACTIONS

Putting this scenario under the MG microscope then, and following along the path of the MG event lifecycle, here’s what I want to make happen:

     
  1. User arrives, an event is called (either the default event if none is specified or the one explicitly called);
  2. Before that event is processed, I ensure that I have in session for this person:
    • a “loggedin” variable with either a value of true or false;
    • a user object, whether it’s empty or populated.
  3. Make those session values available to the rest of my app by placing them in the Event bucket
     
If I do accomplish the above checklist, then any template or process that cares about authentication will need only access the Event object or the Viewstate object (depending on whether it’s a view template as opposed to a controller or model object) in order to find out this user's status.

Okay, so how do I accomplish the above laundry list? Model-Glue comes pre-configured with a generic controller that has methods and listeners already set up to perform whatever action you want at the following times:
  • onRequestStart
  • onRequestEnd
  • onQueueComplete
When you first unpack your Model-Glue sample app, you'll see something similar to the following already present in Modelglue.XML:
<controllers>
    <controller name="GlobalController" type="controller.Controller">
        <message-listener message="OnRequestStart" function="OnRequestStart" />
        <message-listener message="OnQueueComplete" function="OnQueueComplete" />
        <message-listener message="OnRequestEnd" function="OnRequestEnd" />
    </controller>
</controllers>

This is the generic controller I was referring to, and the OnRequestStart listener and method is the one we'll be leveraging. Since OnRequestStart is called before the event itself is executed, it is the perfect place to do ensure that our default values are present and available to the rest of the app at all times.

Here is the onRequestStart method after I modified it to accommodate security:

<cffunction name="onRequestStart" access="public" returnType="void" output="false">
    <cfargument name="event" type="any">
    <cfif not structkeyexists(session,"user")>
        <cfset session.user = getUser().init() />
    </cfif>      
    <cfif not structkeyexists(session,"loggedin")>
        <cfset session.loggedin = "false" />
    </cfif>
    <cfset arguments.event.setValue("loggedin",session.loggedin) />
    <cfset arguments.event.setValue("user",session.user) />
</cffunction>

Here's what's happening.:

Before any event is executed, I am first checking for the existence of a "user" key in session. If it’s not there, I’m creating an empty instance of the user.cfc in my model (which was auto-wired in to this controller…you can see how that's done by checking out the sample app at the end of this post) and putting it into session. I’m then checking for the existence of a "loggedin" key in session, and if not there, creating it with a value of false. After this, I put both session values into the Event bucket and let the event that was called finish its execution.

THE LOGIN PROCESS

Okay, so now we have appropriate values present and available throughout the app. How about the actual logging in process? Let’s walk through it and see how the different parts are arranged. It’s pretty simple (and mostly familiar), you’ll see.

The Process:
  • User is presented with a login form whose action calls the “login” event;
  • The login event makes a broadcast to the authentication controller to perform it’s “login” method, username and password being present in the Event bucket since they were submitted via the login form;
  • The Authentication controller creates an empty instance of the user object, then calls the user object’s ‘login’ method, passing in the username and password arguments;
  • Internally to the User object, IF the user was found based on the credentials passed in, it populates its internal variables (firstname, lastname, id, etc.);
  • The authentication controller checks to see if the User object’s ID value is anything other than the default value of zero. If it IS, then authentication was successful. If it’s zero, authentication failed.
  • If authentication was successful, we put that instance of the user object into session, set session.loggedin equal to true, put both the user object and the loggedin value  into the Event bucket, and add a Result value of “success” so that our event will know what action to take.
  • If authentication failed, we put a message into the Event bucket indicating this (“login failed! Try again!”), and add a Result value of “failure”, again so that our event will know where to redirect to.
Here is my Authentication Controller's Login method:

<cffunction access="public" name="login" output="false" returntype="void" hint="I make the call to log a user in">
    <CFARGUMENT name="event" type="any">
    <cfset var objUser = getUser().init() />
    <cfset objUser.login(username = arguments.event.getValue("username"), password = arguments.event.getValue("password")) />
    <cfif objUser.getID() IS NOT 0>
        <cfset session.loggedin = true />
        <cfset session.user = objUser />
        <cfset arguments.event.setvalue("loggedin",true) />
        <cfset arguments.event.addResult("success") />
    <cfelse>
        <cfset arguments.event.setvalue("loggedin",false) />
        <cfset arguments.event.setvalue("message","Login failed! Try again.") />
        <cfset arguments.event.addResult("failure") />
    </cfif>
</cffunction>


SUMMARY

Those are the items of interest to understand and think about when doing basic authentication. Now, since I know that reading about a thing abstractly is not NEARLY the same as chewing on actual code, I zipped up the whole security skeleton app so you can download it and poke through its innards. It has nothing in it except for the basic security outlined above, but it DOES function (I'm using Querysim in my model).

Also, just for disclaimer purposes, I didn't invest a lot of time making it overly pretty, nor did I comment it as heavily as I normally would. I do believe, however, that it's a very good jumping off point for someone who has been wondering "how to think" about security in an MVC app.

Hope you find it useful!

Doug out.

NOTE 8/24/2009 - I realized long after I did this post that I actually coded in something in a wrong way: the way a new user bean is retrieved. I erroneously auto-wired in the user object to my controller, which causes multiple users to see each others information! What should happen is that the user bean must be gotten directly from the model. If you are using Coldspring (which I highly recommend), then the way you should get a new instance of the user object is by calling "getModelGlue.getConfigBean('user')". In your Coldspring.xml file, you will also need to make sure that you have the Singleton flag set to true, as in "<bean id="user" class="model.user" singleton="true" />". So, instead of this line:

getUser().init()

you would use this line:

getModelGlue.getConfigBean('user')

Sorry for the misinformation! :)



DOWNLOAD SECURITY SKELETON APP ZIP

Posted by dougboude at 3:54 AM | PRINT THIS POST! | Link | 13 comments
21 September 2007
ModelGlue addResult/setValue Order Matters!
Small But Time Consuming Modelglue Gotcha
Attempting a save action, evaluating the outcome, and adding a subsequent "success" or "Check your Work!" message, along with a Modelglue Result value (in order to redirect appropriately) are common things to do when building an app, right? Well, I burned about fifteen minutes this morning trying to do this simple task, tracing events and double checking code, trying to figure out WHY the message value I added to the Event object wasn't present in the final viewstate.

Let me show you two versions of a snippet from my controller. The first snippet resulted in NO "message" value being present, the second one DID:


<cfif retValresult is "success">
    <cfset arguments.event.addResult("success") />
    <cfset arguments.event.setValue("message","Your Dream has been Saved!") />
<cfelse>
    <cfset arguments.event.addResult("failure") />   
    <cfset arguments.event.setValue("message","There's a problem with your Dream...") />
</cfif>
the value "message" was NOT present in my viewstate!


<cfif retVal is "success">
    <cfset arguments.event.setValue("message","Your Dream has been Saved!") />
    <cfset arguments.event.addResult("success") />
<cfelse>
    <cfset arguments.event.setValue("message","There's a problem with your Dream...") />
    <cfset arguments.event.addResult("failure") />
</cfif>
the value "message" WAS present in my viewstate!

Apparently, once Modelglue sees that a Result value has been added to the event object, "that's all she wrote, time to evaluate actions and ignore whatever code follows!"

Just thought I'd share this in case it saves someone else fifteen minutes.
Posted by dougboude at 6:52 AM | PRINT THIS POST! | Link | 2 comments
Dynamically Outputting Query Data 'X' Columns Across
Dynamically outputting query data in "X number of columns across" is no new thing, but since I found myself having to do that very thing this morning, I thought I'd share this sweet little snippet for the benefit of everyone (but mostly for my own snippet collection since I seem to tend to "forget what I once new"... lol).

In this example I'm going to output a series of short questions using the following query result:
coldfusion query result set

WIth the final html and result looking like this:
html table final result of outputting x rows across

<table width="100%">
    <tr>
        <td>Dead loved ones present</td>
        <td>Animal(s) as prominent player(s)</td>
        <td>"Significant Other" present</td>
    </tr>
    <tr>
        <td>Familiar Setting</td>
        <td>Unfamiliar Setting</td>
        <td>Awoke Emotional</td>
    </tr>
    <tr>
        <td>Dreamed you were Awake</td>
        <td>&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
</table>
generated html

Without further adieux, your snippet:

<cfset numColumns = 3 /><!--- set the number of columns to output --->

<!--- determine number of total rows to output --->
<cfif qryQuestions.recordcount mod numColumns eq 0>
    <cfset numrows = qryQuestions.recordcount/numColumns>
<cfelse>
    <cfset numrows = int(qryQuestions.recordcount/numColumns) + 1>
</cfif>

<!--- output the table! --->
<table width="100%">
    <cfloop from="1" to="#numrows#" index="i">
        <tr>
            <!--- create columns for this row 'i' --->
            <cfloop from="1" to="#numColumns#" index="j">
                <!--- calculate which record we're outputting --->
                <cfset qryRow = ((i-1)*numColumns)+j >
                <!--- ...as long as wer're not trying to access a query record beyond our 'recordcount' value... --->
                <cfif qryRow lte qryQuestions.recordcount>
                    <td><cfoutput>#qryQuestions["question"][qryRow]#</cfoutput></td>
                <cfelse><!--- output an empty 'filler' td... --->
                    <td>&nbsp;</td>
                </cfif>
            </cfloop>
        </tr>
    </cfloop>
</table>
Posted by dougboude at 5:34 AM | PRINT THIS POST! | Link | 0 comments
20 September 2007
Adobe User Groups Everywhere: I Have A Dream

I haven't been a member of very many Coldfusion/Adobe-centered User groups, but the few I have participated in seem to have a common, perpetual and prevalent concern floating in the air: membership and attendance.

I have very recently had the privilege of being part of the resurrection of San Antonio’s own Coldfusion Users Group. It is being born out of the existing, more general, Alamo Area Multimedia Users Group due to the sudden influx of hardcore Coldfusion users. Our first meeting was two weeks ago. Nine of us sat in a library and talked about what we wanted to gain by participating in the group, who the group Manager would be, when we would meet, where we would host our site, etc. It was fast paced, and everybody in the room was SO zealous, it was contagious. All levels of skill were represented, and you could just feel the hunger for knowledge that the junior among us harbored. Being a brand new group, however, did not exempt us from the same question of attendance that even well established groups entertain: “How would we increase our membership”?

Here, O Best Beloved, is where the true heart of this post begins, as I found myself engaging in a session of combinatory play while looking at this scenario from a whole other perspective. My first inclination was to think of all the companies in town that I knew used Coldfusion, and then come up with ways to solicit their developers. Then came the epiphany that I must say seemed at the moment (and still does) to be absolutely brilliant:

If we want more Coldfusion developers, then why on earth don’t we just MAKE them ourselves?

We who are intimate with Coldfusion KNOW the deep level of satisfaction and sustenance that it can provide. We know that learning it is not so very difficult, we know the true power that it puts into the hands of a developer, and we know that if more people who already have a propensity for technology and problem solving were exposed to those basic truths, they would very likely adopt it in a heartbeat as we have we.

I raised my hand (keeping things orderly, you know) and when called on shared my idea with the group. The sudden silence and ambiguous faces made me feel as though I had just suggested that the emperor had no clothes, my idea appeared to be so foreign to them. In a moment, though, as they mentally extrapolated what investing the time to teach Coldfusion basics to newbies could mean, they heartily seconded the motion to do so (hammering out the details of who, where, and what will be fodder for the next meeting).

Now, I am not vain enough to think that no other User Group has ever come up with this idea. But again, in all the group’s I’ve had the privilege of sharing time with, and in the midst of all the apparent “worry” over attendance, I never once have heard anybody step outside the mindset of “bring in the Coldfusion developers!” and say “hey, why don’t we grow our own”? Isn’t it a marvelous idea, though? If I were a User Group Manager, or an Adobe corporate brain, and my prime directive was to increase User Group participation, I would be organizing and marketing free “Intro to Coldfusion” classes within my target area. I would put an ad in the Thrifty Nickel; I would post it on Craigslist; I would have all my existing members put flyers up on the corkboards at the office. Shoot, I would bring my kids! (we all know that even a child can learn Coldfusion…) I would follow the true spirit of Evangelism and I would preach Coldfusion to the ignorant and uninformed, not just those already converted. I’d gather as many people as would come and meet in a donated conference room with a few cookies and cokes in the back. I’d fire up my laptop and I would INSPIRE these people! The beauty of Coldfusion’s simplicity and elegant folding together with HTML would be revealed to them; the mystery of the request lifecycle would be elucidated to their previously darkened minds; the POWER of loops and conditionals would be handed over to them; and the secrets of interfacing with amorphous backend data would be made secret no more. When they would leave that one hour revival, those people would walk away filled with the spirit of Coldfusion, empowered and enlivened enough so that they would not soon forget what they had learned. That thin foundation would be the catalyst that would bring them back to the NEXT free session to add yet another layer, until they were brought to the point (as I was so long ago) where they could begin to add onto that foundation for themselves. The end result (in my optimist’s vision, anyway) would be a thriving, zealous San Antonio Coldfusion User Group whose collective affection for their craft would inspire all who approached us.

Wow. I think I’ve just cheerleaded myself to the point where I need to go sleep it off. Lol. But, my point is this, Coldfusion (and other Adobe-oriented) User Group Managers AND MEMBERS: If your membership and attendance leaves something to be desired, don’t be afraid to deviate from “the way we’ve always done it”; get creative, think outside the bun, and why on earth not make the time to grow a few of your own from scratch?  The process of introducing someone to Coldfusion and imparting your knowledge of it to them is SO rewarding and SO educational, most especially to those who are teaching. Everybody profits, the Group grows strong, and Adobe Coldfusion (and other products) lives long and prospers.

Doug out.

Posted by dougboude at 3:44 AM | PRINT THIS POST! | Link | 6 comments
17 September 2007
Just what IS an Object, Anyway?
Making a distinction between the definition of an Object versus a Class is probably not a vital factor in one's OOP abilities. I believe, however, that if we're going to have and use terms, we should do so accurately in order to effectively communicate. I absolutely hate it when people misuse words in ignorance, such as when someone refers to a Cicada as a Locust (a locust is a grasshopper! a Cicada is a Cicada!), or a bat as a rodent (they're both mammals, but that's as close as it gets). For clarification's sake, then, following is what I have discovered to be the definition of an Object in a Coldfusion context.

When asked the question "what IS an object?", I nearly always respond with a description that includes the phrase "living and breathing". Here's why:

According to science, the basic checklist to use when determining if something is alive or not is:

  • Can it Reproduce?
  • Can it Obtain and use energy?
  • Can it Grow, develop, and die?
  • Can it Respond to the environment?

Once you issue the command

<CFSET objFrankenObject = CreateObject("component","model.Frankenstein") />

Coldfusion takes what is a lifeless blueprint (the Frankenstein.cfc file) and breathes life into it, creating something that meets every one of these criteria!
  • Can this Creation reproduce? It certainly does have the potential, depending on how it was designed.
  • Can it grow, develop, and die? Abso-frickin-lutely! It occupies a certain amount of ram, and I guarantee you that as soon as I call one of its setters and stuff a large structure into it that it will grow and then occupy MORE ram! If I <CFSET objFrankenObject = "", it'll be dead and gone.
  • Can it respond to its environment? Of course; that is what it was created to do in the first place.
  • Finally, can it use and obtain energy? Without spending too much effort in attempting to stretch the analogy into that realm...let's just say 'yes' since our server is plugged into an active outlet.
By all accounts, this Object is a living, breathing, programmatical entity. This way of thinking about objects in contrast to the flat, lifeless CFC/Class (that so often is referred to as an object, erroneously in my opinion) makes the distinction between them crystal clear.

In a nutshell then:

Object = living breathing instance of a CFC (Class)


Again, thinking of objects in this way may not make one a better or worse OO programmer, but it definitely doesn't hurt to have a finer understanding of what the terms truly represent, right?


If you missed the post "Just What IS a Class", it's the complement of this one and might make good prerequisite reading since it defines what an object IS NOT.

(this definition along with all the others I've collected over the past year can be found in my personal OO Lexicon)
Posted by dougboude at 12:34 PM | PRINT THIS POST! | Link | 0 comments
14 September 2007
Just What IS a CLASS Anyway?
OO Terms in a Coldfusion Context
Which phrasing is correct:

1."Hey boss, take a look at this object I just wrote!"
2."Hey boss, take a look at this class I just wrote!"

The answer is number 2. After reading the following definition, hopefully the phrasing in choice 1 will sound strange to you.


The term "CLASS" tends to be misused, or even UNDERused in OO conversations with regard to Coldfusion, and is very often omitted in favor of the term Object. There is, however, an important distinction between the two. Let's illustrate this by asking a simple question that I KNOW you know the answer to.

Can you open a CFC in notepad?

The answer is "Heck yeah!" Why is this true? Because in reality, a CFC is JUST A TEXT FILE with a '.cfc' extension. It's the content, however, of this text file that makes it special, because what it contains are the blueprints that Coldfusion uses to create living breathing objects. So, when you tell Coldfusion something like


<cfset objUser = createobject("component","model.user")>


 you're actually saying "CF, go find the user.cfc file, open it up, read the contents, and give me back a living breathing OBJECT that is built according to the blueprints."

In a very small nutshell then,

A Class is the definition used to create an object.

The class tells Coldfusion what methods to create, what variables are available, what items are returned...gosh, it tells CF every little detail about how the finished product (object) should behave and respond!

Class = CFC = Blueprint
 
For contextual illustration, a Java ".class" file is also just a text file containing the blueprints that Java needs in order to construct a Java object. A CFC is a Coldfusion class containing the blueprints CF needs in order to construct a Coldfusion object.


 The plan...the blueprint...the CFC... it's what the rest of the OO world refers to as a CLASS, and so should you!

(this definition along with all the others I've collected over the past year can be found in my personal OO Lexicon)
Posted by dougboude at 6:12 PM | PRINT THIS POST! | Link | 1 comment
12 September 2007
Snippet: Outputting Reactor Validation Structures
Reactor's built-in validation produces a validationError structure when it encounters something amiss, and passes it back to your viewstate. That structure is actually a structure of arrays.

I DO use Reactor's validation, and found myself copying and pasting the same snippet for outputting the validation error results a time or two this morning... figured it might come in handy for other folks, too.

code from my view template...
<cfset validation = viewstate.getValue("[name of error structure here]", structnew()) />

<cfif structcount(validation) gt 0>
    <cfoutput>
        <cfloop collection="#validation#" item="v">
            <cfloop from="1" to="#arraylen(validation[v])#" index="i">
                #validation[v][i]#<br>
            </cfloop>
        </cfloop>
    </cfoutput>
</cfif>


A couple of things to remember:

1. The "[name of error structure here]" value is determined by the value you gave it in your event

sample generic Commit showing where the name of the validation error structure is designated...
<message name="ModelGlue.genericCommit">
    <argument name="recordName" value="ClientRecord" />
    <argument name="criteria" value="ClientID" />
    <argument name="object" value="Client" />
    <argument name="validationName" value="PreferenceValidation" />
</message>


2. When calling the viewstate.getValue() method, you can specify a default value to use if the item isn't found. In my snippet, I specified that "if the validation structure isn't there, just give me back an empty structure so my code won't break".

That's it!
Posted by dougboude at 1:23 PM | PRINT THIS POST! | Link | 0 comments
Reactor: A LOT Like a Wendys Drive Through
Okay, I've been up since 3 A.M (went to bed waaay too early last night) working on a Modelglue project, and now I'm feeling the need to rant a little bit. Not complain, per se, because I truly do appreciate the blood, sweat, and tears that must have gone into creating the frameworks that comprise Unity; But out of the 4 hours I "worked", a full third of it was spent on issues related to trying to get my app to "see" certain kinds of changes, even WITH my cheat sheet (which itself was born out of a lot of time spent pulling my hair and bumping around in the dark). That's just a wee bit much for something that's supposed to help me spend my time more efficiently, wouldn't you agree? In particular, this morning my beef is with Reactor.

Reactor has a Development mode and a Production mode. In theory, I should put this baby in Dev gear and just leave it there until I'm ready for production. But, Dev mode is just plain slow as molasses in January, so I opt rather to work with Reactor in production mode until and unless I make a change directly related to the database. This morning happened to be one where I had added some fields to a table. So I make my table changes, change Reactor to Development mode, and reinit the app. I then walk through my app as a user would to the point where I KNOW this particular table's record object is needed. It HAS to do be done this way, you know. If I didn't, Reactor would not have re-created my object for me, DESPITE the fact that I re-init'd the app, because it is a WHOLE lot like a Wendy's drive-through: neither one creates it until you actually order it. Okay, so now I am able to successfully edit and insert records containing values for these new fields, so I must have ordered my burger right (pun intended). As usual, I now switch back to production mode for efficiency's sake. More testing, and I realize that something's amiss with one of my fields...I'm getting a sql error when trying to do an update after having edited a value. BUT HEY NOW! WAIT JUST A MINUTE! HOW can I be getting a SQL error when I'm using Reactor validation to check values before attempting to insert them? This cannot be! And yet, it is. After fiddle farting around with double checking syntax, making sure quotes were correct, field names and form field names correlated...it finally occurred to me to open up the Reactor validator object for this table, JUST TO MAKE SURE it looked right. Surely it would be right. After all, I KNOW I did what Reactor required of me to regenerate that table's objects. Sure enough, though, no validators existed for the new fields. DANG IT! Apparenly Reactor only regenerated the SPECIFIC Reactor objects I needed for the functions I performed before switching back to production mode. Back to development mode, re initialize the app, walk through as a user to the point where I INVOKED VALIDATION, then all was well.

When I finally DID get everything working well and regenerated in my local environment, I then committed my changes via SVN to the repository and ran the update for our testing environment. We aren't including Reactor's base project CFCs in our repository, so now it was time to switch to dev mode in Test and regenerate. Ay, here I go again, having to login in as a user and physically "touch" the app in a lot of places to force Wendy...er, Reactor, to make my burger. You can imagine how much time it can potentially take when you have to go into the app and USE it in order to make changes happen on the backend like that. And what if you don't know the app from the front end? What if you're job only entails backend work? Then you're either forced to learn the app, or wait for your testers to go in and tell you if it worked or not. Either way, it's not efficient use of time.

My rant is just this: why's it gotta be so painful to use Reactor? Why do I NEED an initialization cheat sheet when developing with it? If I've jumped through the fiery hoop to get Reactor to regenerate my table's record object, why can't it just regenerate ALL of the objects for that particular table in one fell swoop??? WHY am I FORCED to know the app from the FRONTend in order to manifest a change on the BACKend (Reactor's whole, "I won't make it for ya till you order it!" philosophy)? What if all I was hired to do was back end coding and don't know the app well enough from the user's perspective to properly navigate my way through to where my code change lives?

Perhaps I'm just using this awesome tool the wrong way; I don't think so, though.  I've worked with several other people together on MG projects, I've read just about everything out there that has to do with MG:Unity, I've got a LOT of hours logged getting my hands dirty with this framework, and I haven't seen anybody else do it any different than I. If I had a magic wand, I would wave it and miraculously "init=true" really WOULD reinitialize my app completely!  As it stands now though, it's kind of a pain.

Okay, I'm done venting. Thanks for listening. ModelGlue:Unity, I still love you.

Doug out.
Posted by dougboude at 12:29 PM | PRINT THIS POST! | Link | 4 comments
11 September 2007
last time I promise...Coldfusion is Dead!
Sys-con and some of the other brilliant prognosticators out there were my source of inspiration this morning on the drive in to to work...here, have a look-see into my unique mind....

CF is dead!
Adding the name Dave Lowe, for google indexing's sake.... :)
Posted by dougboude at 4:49 PM | PRINT THIS POST! | Link | 0 comments
My Blog is Now MUCH More Print Friendly
Just an FYI for those who might be interested...

At the suggestion of a recent reader of my blog, I have made it MUCH easier now to print out any posts of interest. At the bottom of each post is a link which will render it for you in a new window (and in a format I'm sure you'll recognize).

If you do give this a whirl and find any posts that cause the process to "not work so good", please let me know about it!

Thank ye.

The Management
Posted by dougboude at 4:00 AM | PRINT THIS POST! | Link | 0 comments
07 September 2007
ModelGlue Development Cheat Sheet
When my team and I first began building our Model-Glue:Unity application, we experienced a GREAT deal of head scratching and frustration during the development process. We would alter a line here or there, refresh our browser, and NOT see the change. It took us quite some time to finally figure out what kind of action was warranted for what kind of change in order to make it manifest, so we documented it for ourselves in our local Wiki.

Following are the guidelines and shortcuts we came up with to make our development a wee bit less frustrating. I don't claim to have the market cornered on understanding all of the nuances and functionality of MG:U, so please, feel free to let me know about any changes or additions you feel would benefit those who read it.


ModelGlue:Unity - Modes of Operation

(Note: ALL of the settings required to perform the following functions reside within the Coldspring.XML file)

There are essentially two modes of operation in ModelGlue: Development and Production. What each of these modes entails is actually user-definable since MG provides several different options that can be associated with each. For instance, you can be running your application in "production" mode and yet still opt to display Model-Glue debugging information. For the purposes of this post, "Production" will imply that all options associated with development have been disabled and the application is configured to run at its maximum speed.


How to Toggle Modes


In order to toggle between development and production modes:
  • change the "reload" property in Coldspring.xml to "true" for development or "false" for production;
  • edit the "debug" property, setting the value to "true" for development or "false" for production;
  • ensure that the "Mode" property in the Reactor bean definition is set to the value "Production" for production, or "Development" for development;
<property name="reload"><value>true</value></property>
<property name="debug"><value>true</value></property>

<bean class="reactor.config.config" id="reactorConfiguration">
    ...
    <property name="mode"><value>development</value></property>
</bean>
settings for development mode

<property name="reload"><value>false</value></property>
<property name="debug"><value>false</value></property>

<bean class="reactor.config.config" id="reactorConfiguration">
    ...
    <property name="mode"><value>production</value></property>
</bean>
settings for production mode

Refreshing During Development

Because ModelGlue:Unity maintains some objects, values, and settings in scopes that aren't typically refreshed, certain types of modifications require certain actions in order to make them manifest. The following matrix covers the typical mods that require specific reset actions.
ModificationReset Action
Change to ModelGlue.xml (or any XML file included in Modelglue.xml)
  • Make sure the "reload" property in Coldspring.xml has a value of "true";
  • re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Changes to Coldspring.XML
  • Make sure the "reload" property in Coldspring.xml has a value of "true";
  • re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Change to a controller cfc re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Change to a model cfc all changes should be visible real time...just refresh your page!
(note: IF the cfc you edited ALSO happens to be injected into a controller, then you'll have to follow the steps for "changes to a controller cfc")
Change to the database Reactor needs to be reset. This is accomplished by:
  • In the Colsdspring.XML Reactor Configuration Bean, make sure "Mode" is "development";
  • Make sure the "reload" property in Coldspring.xml has a value of "true".
  • If Scaffolding is being used, ensure that Scaffold is set to "true".
  • Once the settings are verified, reinitialize the application by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true).
After the application finishes reinitializing, you will very likely wish to DISABLE scaffolding and set your reactor mode back to development, then RE-INITIALIZE again for those changes to take effect.

CRITICAL NOTE! Reactor is a LOT like Wendy's...it doesn't make till you order it. What this means is that, even though you may have reinitialized the app AND Reactor is in Development mode, that one little record object you needed to be recreated WON'T be recreated UNTIL you actually call the portion of your code that asks ModelGlue for it. SO, be sure you leave Reactor in Development mode UNTIL you get to the portion of your code that actually USES the object you were trying to refresh. Ya feel me?

relevant portion of Coldspring.xml file
<bean id="reactorConfiguration" class="reactor.config.config">
    ...
    <property name="mode"><value>development</value></property>
</bean>

<property name="rescaffold"><value>true</value></property>
Change to a View All changes should be visible real time...just refresh your page!
Change to Reactor Dictionary file (model/data/reactor/
Dictionary/[table name].xml)
All changes should be visible real time...just call your event again!

Again, any additions or corrections that need to be made to this matrix, please shoot me an email (via my funky resume) or leave a comment (if you can pass the Turing Test).

Doug out.
Posted by dougboude at 12:14 PM | PRINT THIS POST! | Link | 3 comments
29 August 2007
"My Boss Wants To Know Why We Should Create Objects"
A good friend of mine IM'd me this morning, hot in the middle of a sales pitch to his boss as to why it was a good idea to get away from the Spaghetti paradigm and move towards some semblance of OO methodologies. Our conversation was short and sweet, but I thought I'd toss it out here in case anybody else has anything to add (or correct), for the benefit of any other developer who finds themselves in a similar "sales" position.

The Conversation

MyBuddyJack: My boss wants to know, why do we have to create instances of objects and go that route? Why can't we just use cfinvoke to get the data we need and just code everything an object needs right in the same cfc.
MyBuddyJack: I think he is wanting to code things procedurally, but with cfcs...that's the feeling I get. But what is the advantage to coding it more OO by creating instances etc.?
MyBuddyJack: I just told him it is easier to maintain and change down the road.
Doug Boude: persistence
MyBuddyJack: I couldn't really sell it like I wanted.
Doug Boude: for example, i have a user object. If I instantiate it, then I can keep it alive and interact with it, changing things about it here and there and maintaining those changes throughout the life of the object
Doug Boude: if I just use invoke, I'm treating it as nothing more than a dumb collection of functions
MyBuddyJack: he wants to put everything into the session scope
MyBuddyJack: so everything is always available anywhere
Doug Boude: and what would BE in session? Oodles of variables sitting there beside one another, all in the same bucket, differentiated only by some naming convention, if that?
Doug Boude: using a CFC as an object, you collect related things together, so you always know where to go for a specific thing
Doug Boude: if you didn't use a User object, I'm POSITIVE he'd probably create a User structure that lived in session to hold all of the user's info
Doug Boude: but, the methods he needed to act upon and with that user info would all be living here and there and everywhere...little semblance to anything truly organized and encapsulated.
Doug Boude: to move that user functionality, then, at some later date would be a nightmare because SOME Of the user stuff would live in application.cfm, some of it would live in some UDFs, some would live here, some would live there...no encapsulation
Doug Boude: it would be what we have always been used to: spaghetti
MyBuddyJack: Yeah that's a good point. I told him that if he has an instance of a User Object, then when he wants to output stuff he just calls the object, whereas otherwise he'd have to invoke everytime to get the data he wanted.
Doug Boude: but stuff it all into a user CFC, and voila! it's always packaged up, ready to drop into anything
Doug Boude: invoke is expensive...it instantiates the object first, then calls the method, then destroys the object again
Doug Boude: extra overhead
Doug Boude: why not just breathe life into it ONE time initially and have it waiting to be called upon instead?
Doug Boude: i could only see using cfinvoke under two circumstances
Doug Boude: one, when my CFC is truly just a collection of random methods that I will need on occasion only
Doug Boude: or two, when I need to call dynamically named methods, which isn't possible on an already instantiated object
MyBuddyJack: I think I understand encapsulating everything as a benefit. But is that it? What other beneifts are there to instantiating objects OR why is that better than procedural?
Doug Boude: you can instantiate objects and still code procedurally
Doug Boude: it's really two different things
Doug Boude: using CreateObject does not an OO application make
Doug Boude: like i said, there's the savings of overhead; there's the logical arrangement and grouping of both functions, business logic, and data
Doug Boude: there's the re-usability factor, should that ever be a desire
Doug Boude: if you use objects as the main basis for your app, then one day when the light DOES go off for them and they see the beauty in MVC, moving to a new framework won't be nearly as large a pain
Doug Boude: it's MUCH easier to make modifications to an app when you have things arranged in objects, and without having that old common spaghetti coder's fear, "What else is going to break if i change this?"
Doug Boude: but putting things into CFCs and then using CFINVOKE to interact with them is like starting your car long enough to get you to the first stop sign, then turning it off again. then starting it up again when you're ready to proceed, then turning it off again at the next red light. etc.
Doug Boude: in that analogy, it almost looks...ridiculous to do it that way
Doug Boude: and, it's easy to see the extra overhead involved with that analogy...extra wear and tear on your starter, extra time needed to actually respond to the light turning green, etc. Same thing in an app
Posted by dougboude at 10:58 AM | PRINT THIS POST! | Link | 3 comments
The Model-Glue Event Lifecycle in Layman's Terms
Okay, actually there's no way to COMPLETELY distill this topic down to the point where you don't have to have a decent understanding of web development to grasp it. BUT, I do believe that I've been able to demystify the subject sufficient to allow even the beginner OOP/Model-Glue developer to "get it", which is what I wish I would have had a year ago when I left the comforts of procedural and embarked on my OO journey. That being said, this post is not one of the shorter ones I've done; necessarily so, as there's a lot of ground that I felt couldn't afford to be skimmed over. By taking the time to read through it, however, I do believe that a LOT of the "gray areas" that we OO newbies face, architecturally speaking, will be cleared up enough to at least give us a clue which direction to go. That being said, what follows is my PERSONAL understanding (which has worked well for me) of the Model-Glue Event Lifecycle. Enjoy.

UNDERSTANDING THE MODEL-GLUE EVENT LIFECYCLE

The Model-Glue framework can present an intimidating learning curve when coming directly out of the procedural world to that of Object Oriented Programming. One of the things that will help the Model-Glue student immensely is to have a solid understanding of how the Model-Glue event lifecycle works: being able to visualize the invisible. With this knowledge in pocket, many of the architectural questions that will inevitably arise will be more easily addressed. So, let’s explore what we mean by the term ‘lifecycle’, what the definition of ‘event’ is in a Model-Glue context, and what Event’s role is in a Model-Glue application!


 A Lifecycle You Already Understand

 Let’s look at a lifecycle you’re already familiar with: http request. Pictures are worth a thousand words, so allow me to illustrate this:


Looks familiar, doesn’t it? The request begins life when the client initiates it with an http call to our web server for a specific template. The server receives the request, grabs the template requested, and hands it over to the Coldfusion server for processing. The Coldfusion server renders the CFM template into pure HTML (since that’s all a browser understands), hands the HTML back to the web server, and the web server returns the HTML to the client. End of the request’s lifecycle!

 
When a client makes a request of a Model-Glue application, a similar process takes place. The focus of this post will begin (and end) at the point where index.cfm is requested and passed a parameter called “event” (eg; http://www.myMGapp.com/index.cfm?event=home). The receipt of this parameter is the first breath our Model-Glue application draws, and begins the internal process which we will now follow through to its conclusion.

 

What Exactly IS A Model-Glue Event?

‘Event’ is two distinct things:

  • ‘Event’ is a simple value: a name, such as ‘home’, or ‘do.login’ (yes, compound event names are acceptable and even advantageous for organization’s sake!);
  • ‘Event’ is a bucket.

 
 “A bucket?”, you may be asking yourself. Yes, a bucket! Buckets carry things around, and at a certain point in our event lifecycle study we will see how the event changes from being simply a name to an actual value-carrying OBJECT that gets passed around within the application! It’s a beautiful metamorphosis, as you will see.

 The Event Lifecycle in a Nutshell

 

Let’s begin with an overview of what takes place in the lifecycle of a Model-Glue event (We’ll be exploring each step in detail later):

  1. A request is made, notifying model-glue to execute a specific event (eg; http://www.myMGapp.com/index.cfm?event=do.login). At this point, Model-Glue generates an empty Event Object (aka: bucket).
  2. Model-Glue looks in its modelglue.xml file to find out what messages to broadcast to listening controllers ( <broadcast><message name="login" /></broadcast> ) for the 'do.login' event.
  3. Controllers listening for the "login" message execute their corresponding functions ( <controller name="AuthenticationController" type="controller.AuthenticationController"><message-listener message="login" function="AuthenticateUser" /></controller> ).
  4. Model-Glue executes any relevant result actions (behaves as an 'if' statement), if present.
  5. Model-Glue renders any views that are defined for this event. HTML is delivered and the event lifecycle is over.

 
Event Lifecycle, Step One: Create the Event Object (aka: bucket)

 
The whole event lifecycle depends upon values being passed around within the application, so the first thing Model-Glue does after being notified which event to execute is to create an Event Object. Just visualize it as a bucket at this point.

 After the Event object is created, any incoming form fields and URL parameters are immediately stuffed into it for safekeeping.

 
Event Lifecycle, Step Two: Reading the Event Definition

 Now that Model-Glue has the Event Object all populated with incoming values, it’s time to discover exactly what should be done with them for the event name that was called. The instructions, or event definitions, that Model-Glue refers to when an event name is received all live in an XML document called “Modelglue.xml”. Here is what an event definition looks like:
 

<event-handler name="do.login">

            <broadcasts>

                        <message name="login" />

            </broadcasts>

            <results>

                        <result name="success" do="home" redirect="true" />

                        <result name="failure" do="login" redirect="false" />

            </results>

            <views />

</event-handler>


Summary of steps 1 and 2

The <event-handler> tag has three children: <broadcasts>, <views>, and <results>, and they are evaluated in that order. Let’s look at each one more closely, following our Event Object through the process.

 

Event Lifecycle, Step Three: Giving Shout-Outs (aka: making broadcasts)

 The first thing Model-Glue does for a given event is to make any defined broadcast. A broadcast is simply a shout out that triggers certain CFCs to execute specific methods. In our example, the broadcast “login” is made. Model-Glue looks in another section of its Modelglue.XML file to find out what CFCs are “listening” for that particular message, then executes the relevant method. In this scenario, this is what Model-Glue found in it’s XML file:

 <controller name="AuthenticationController" type="controller.AuthenticationController">

            <message-listener message="Login" function="Login" />

            <message-listener message="Logout" function="Logout" />

</controller>

 
Ah, so Model-Glue is going to invoke the “Login” method of the AuthenticationController.cfc (which lives in the “controller” directory). Here is an important thing to know at this point: for every method that Model-Glue calls, the Event Object that was initially created and populated will be passed in as an argument, like so:

 <cffunction name="Login" access="public" returnType="void" output="false">

            <cfargument name="event" type="ModelGlue.Core.Event" required="true">

            ….

</cffunction>

 
Why? So that our methods can read values out of it (such as the username and password that were passed in via our login form) AND so our methods can put values INTO the bucket as well! In our example, the Login method is going to attempt to authenticate a user. If it succeeds, it’s going to add a ‘Success’ result to the Event bucket; if authentication fails, it’s going to add a ‘Failure’ result.


Summary of Step 3

Okay, the “Login” method finished, Model-Glue has the Event Object in hand, and there are no more broadcasts defined for this event, so let’s go on to the next step in the Lifecycle.

 

Event Lifecycle, Step Four: Decide What Results to Execute

 Take a look at the <results> section of the event definition. We see two named results defined (so called because each of them has been given a specific name): success and failure. Recall how our Login method added one of two possible results to the event bucket, either “Success” or “Failure”. At this point, Model-Glue looks for any results in the Event bucket and executes whichever one is appropriate. Executing a result really just means “hey Model-Glue, you found a result named “Success”, eh? Then I want you to jump to the defined event named “home” and execute that now.” If a result named “Failure” had been found, then the “login” event would have been called, presenting the user with the login form again. When Model-Glue executes a named result and jumps to another event, the programmer has two choices at this juncture. To start the new event fresh with an empty Event object, or to carry the existing, populated Object over to the next event. This is controlled with the <result> attribute “redirect”. If set to ‘True’, a new, empty Event object is created; if set to “False’, the original Event Object is passed along.

Summary of Step 4

At this point, all of our messages have been broadcast and relevant methods executed; if we needed to redirect to another event, that was also already accomplished. We’ve now arrived at the final step in the event lifecycle: Render our HTML and send it back!

 

Event Lifecycle, Step Five: Render The Views!

(aka: create the HTML that will be passed back)

 Since our “do.login” event was all about actions and not views, let’s look at the “home” event that a successful login attempt redirects us to:

 <event-handler name="home">

            <broadcasts />

            <results />

            <views>

                        <include name="body" template="dspMainContent.cfm" />

                        <include name="final" template="dspLayout.cfm" />

            </views>

</event-handler>

 This particular event doesn’t ask Model-Glue to make any broadcasts nor does it ask Model-Glue to look for and process any results. What it DOES do, however, is ask Model-Glue to render two different CFM templates, dspMainContent.cfm and dspLayout.cfm. There are several important things to note here.

 First, that we can tell Model-Glue to render any number of individual CFM templates. Second, notice how each template is given a “name” attribute…the rendered HTML will later be referred to by this name. Third, the order we list them in is irrelevant with one WHOPPING exception: the last template rendered is the ONLY one that Model-Glue will return to the browser. So what happens to the other Views that were rendered and placed onto the Viewstack (notice how I just increased your Model-Glue vocabulary ;) )? All other rendered templates are “included” in the final template, in nearly EXACTLY the same manner as using a CFINCLUDE, only slightly different. See a previous post I did on Views called “Model-Glue Views Demystified” for more detailed information on this topic. Now, let’s find out where our Event Object/Bucket is relative to the views that are being rendered, shall we?

 Model-Glue makes the contents of the Event Object available to your CFM view templates in an object referred to as “ViewState”. It, too, is an object, very similar to the Event Object, and it contains all the values that your template could possibly need. You may be asking yourself, “why do we need the ViewState Object if we already have a perfectly good Event Object?”. Valid question, and the answer lies within the philosophy of object oriented programming and the two cardinal virtues that a framework like Model-Glue seeks to uphold: Encapsulation and Autonomy. Suffice it to say that in order not to violate basic MVC principles, copying the contents of the Event Object into the ViewState Object was the better thing to do. Okay, take a look at the following two lines of code taken from within a view template:

 <CFSET firstname = ViewState.getValue("firstname") />

 <CFOUTPUT>Welcome, #firstname#!</CFOUTPUT>

 Any CFM template created for use within a Model-Glue application can absolutely count on the presence of “ViewState”. The value “firstname” that we’re retrieving from ViewState is a value that was placed in the Event Bucket at some point previously in the event lifecycle, likely by one of our broadcasts. Are you seeing the beauty of the Event Object yet? From the beginning of our event call, that bucket was passed around here and there, values being put into it, redirections being made as needed, until finally we arrive at our views where all of the previously saved values are made available for use within your templates. Strings, Arrays, Structures, Queries, and even other Objects can be stuffed into the Event Bucket and made available via ViewState. No limitations in that arena!

Slightly off topic from this article, let’s quench some curiosity and sneak a quick peek at how the final view being rendered includes content previously rendered in the viewstack, in this case, how “body” is output within “final”. Take a gander at the following few lines of code:

 <cfset body = ViewCollection.getView("body") />

 <div class="contentbody">

<cfoutput>#body#</cfoutput>

</div>

Yes, it’s really that simple. All rendered views are placed onto the viewstack, referenced by the name we gave them in the event definition, and are available within our template by accessing the “ViewCollection”.  Again, for more detail on Model-Glue views, see the “Model-Glue Views Demystified” post.

Views all rendered and the final HTML complete, Model-Glue returns it to the web server, which in turn returns it to the browser that requested this particular event in the first place.


Summary of Step 5, and the end of our Event Lifecycle

This is the end of our Event’s life; it is disposed of properly. RIP.

 
One Final Bit of Trivia

If you’re curious about what the Event Object really looks like, here’s a CFDUMP showing it and its methods:


CFDUMP of the Event Object

Conclusion

 As its name implies, Model-Glue is a framework that allows a developer to create code that is truly self-contained and reusable by acting as the glue that binds all the pieces together. It accomplishes this by managing the one common denominator that the Model, View, and Controller all have in common: The Event Object. From the client’s initial call to the final delivery of the requested HTML to the browser, it is the Event Object that the developer must understand and interact with in order to create a working application. Only a mid-level understanding of the event lifecycle is required to meet the majority of architectural needs. How to incorporate security, how to ensure the display of appropriate navigation, how to easily add in new functionality: these are all common challenges that understanding the Model-Glue event lifecycle will enable any developer to overcome.

Posted by dougboude at 1:04 AM | PRINT THIS POST! | Link | 3 comments
23 August 2007
COLDFUSION IS DEAD! So says Dave Lowe
My friend Jim just turned me on to a blog post he came across that attempts to prove that "Coldfusion is dead". Of course, even without reading such a post it's easy to properly classify it as complete idiocy...it is an interesting read, though, and it does solicit input from the community of apparently blind CF developers who have embraced a corpse of a language. The guy wants to know if we exist or not. I posted a comment asking him to elucidate on the standard he and his IT department used to formulate their judgment; hopefully he will.

Myself, I'm of the opinion that the true motivation of his post has more to do with the fact that he's immersed well into the heart of "The Land of the Fruits and Nuts" than anything else. ;)

Happy reading!  ColdFusion is Dead, by Dave Lowe
Posted by dougboude at 11:20 AM | PRINT THIS POST! | Link | 5 comments
13 August 2007
OO Lexicon Available as Webservice
Some time back someone asked me if I would consider exposing my personal OO lexicon as a webservice. My answer was yes. However, I didn't get around to doing it until just today. I've decided to secure it, so if you're interested in consuming it, just email me (email address buried in my funky "Yahoo Personals" style resume) and I'll likely give you access to it. Okay, here are the particulars:

webservice address: http://www.dougboude.com/webservices/DBLEXICON.cfc?wsdl

Return type: either XML or native CF Query, that's up to the consumer

Sample call and processing for XML return:
<cfset ws = createobject("webservice","http://www.dougboude.com/webservices/DBLEXICON.cfc?wsdl") />
<cfset lex = ws.getLexicon(username=[your username],password=[your password],toXML = 1) />
<cfset xPath = "//ITEM">
<cfif isxml(lex)>
    <cfset terms = xmlSearch(lex,xpath)>
    <cfoutput>ITEM COUNT: #arraylen(terms)#</cfoutput>
    <br>
    <cfloop from="1" to="#arraylen(terms)#" index="i">
            <CFOUTPUT>#terms[i].Category.xmlText# <STRONG>#terms[i].term.xmlText#</STRONG> - #terms[i].definition.xmlText#</CFOUTPUT><br />
    </cfloop>
</cfif>

Sample call and processing for CF Query return:
<cfset ws = createobject("webservice","http://www.dougboude.com/webservices/DBLEXICON.cfc?wsdl") />
<cfset lex = ws.getLexicon(username=[your username],password=[your password],toXML = 0) />
<cfoutput query="lex" group="category">
        <h2>#lex.Category#</h2>
        <cfoutput>
            <STRONG>#lex.term#</STRONG><br />#lex.definition#<br />
        </cfoutput>
        <hr />
</cfoutput>

Since I'm requiring authentication upon invocation, and since the contents of the lexicon aren't changing all that often, I recommend that you just cache the results after the first call and refresh once a day or so.

Oh, one last thing while we're on the subject...

I know that my lexicon is much less than fully comprehensive, so if anybody has any suggested terms to add and a starter definition to go along with it, I'd be glad to consider them for addition.

Doug out.
Posted by dougboude at 5:56 PM | PRINT THIS POST! | Link | 4 comments
12 August 2007
Accommodating Dynamic Terminology in your App
When building applications intended to service multiple clients, the ability to easily customize certain aspects is a must. In the health benefits management arena, this need couldn't be any greater, most notably with regards to terminology and phrasing. Whereas one client may wish to refer to their employees' usage of tobacco as "smoker certification", yet another will insist on calling it "tobacco usage".  In order to accommodate this dynamic requirement, my team and I came up with what we dubbed our "Lexicon" component, so I thought I'd share the basics of it here in case anybody else has a similar need.

The overall concept and approach is this:
  • Every term is given a generic name;
  • Every term will have a default value with the ability to have customized override values for specific clients;
  • Every term is given a language identifier, to accommodate the fact that languages other than english may be needed;
  • The component that performs lexicon lookups is made available to every display template in some global scope (application, session, request, viewstate (when using Modelglue), etc.);
  • Terms are output inline within the display template via a "getTerm" method call;

Firstly, let's look at the table used to store the lexicon data:


  • 'ID' is an autonumbering field;
  • 'itemID' contains the generic reference to the term. We utilized a hierarchical naming convention that identified the specific area of the app the term applied to, but any identifier could be used here, including a simple GUID;
  • 'ClientID' is either NULL (indicating this is the generic default term to use) or contains the ID of a specific client (indicating that we should use this client's term for this item);
  • 'term' is the actual text that will be displayed;
  • 'languageID' is the id of a language record from our 'languages' table (a simple list of languages we wanted to support). In our case, 1 = english, 2 = spanish, 4 = rap;
MSSQL script to generate the lexicon table:
CREATE TABLE [dbo].[Lexicon] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [itemID] [varchar] (75) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [clientID] [int] NULL ,
    [term] [varchar] (250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [languageID] [int] NULL
) ON [PRIMARY]
GO


Simple enough, right? So, if we have a title that needs to be displayed within the Third Party Administrator pod (see illustration), we create an itemID of "pod.tpaadmin.title", providing a record for the default wording, one for our client 690 who wasn't happy with our default wording, a default spanish label, and a default rap label.

Now, we need a Lexicon object to perform lookups for us. The relevant method we'll be using ("getTerm") will look for this client's override value first. If it finds it, it will be returned. Otherwise we'll grab the default value. Here's our "getTerm" method:

<CFFUNCTION name="getTerm" returntype="string" access="public" hint="I return the appropriate term for a given itemID" >
    <CFARGUMENT name="termID" required="true" type="string" hint="I am the id of the term we're looking for. Typically this will be either a string or a UUID" />
    <CFARGUMENT name="languageID" type="numeric" required="no" DEFAULT="1" hint="I am the id of the language we want to use. default is 1 for english." />
    <CFARGUMENT name="clientID" required="true" type="number" hint="I am the id of the client associated with this lookup." />
    <CFARGUMENT name="DSN" required="true" type="string" hint="I am the DSN to use for queries." />

    <cfset var local = structnew() />
   
    <!--- look for the client's override value first... --->
    <CFQUERY name="local.getTerm" DATASOURCE="#arguments.DSN#">
        SELECT term
        from lexicon
        where
        clientID = <cfqueryparam value="#arguments.clientID#" CFSQLTYPE="CF_SQL_INTEGER">
        AND
        languageID = <cfqueryparam value="#arguments.languageID#" CFSQLTYPE="CF_SQL_INTEGER">
        AND
        termID = <cfqueryparam value="#arguments.termID#" CFSQLTYPE="CF_SQL_VARCHAR">
    </CFQUERY>
    <cfif local.getTerm.recordcount neq 1><!--- nothing client-specific returned, grab the default term --->
        <CFQUERY name="local.getTerm" DATASOURCE="#arguments.DSN#">
            SELECT term
            from lexicon
            where
            clientID IS NULL
            AND
            languageID = <cfqueryparam value="#arguments.languageID#" CFSQLTYPE="CF_SQL_INTEGER">
            AND
            termID = <cfqueryparam value="#arguments.termID#" CFSQLTYPE="CF_SQL_VARCHAR">
        </CFQUERY>
    </cfif>
    <CFRETURN local.getTerm.term />
</CFFUNCTION>

Last but not least, here's how we utilize the Lexicon object within our display template (using Model Glue's 'Viewstate'...could just as easily be application, session, or request):

<cfset lex = viewstate.getValue("lexicon") />

<span class="contentTitle">
 <cfoutput>#lex.getTerm(termid="landing.pagetitle",clientID="690",DSN="#application.dsn#")#</cfoutput>
</span>
<br />
<span class="subContentTitle">
 <cfoutput>#lex.getTerm(termid="landing.subtitle",clientID="690",DSN="#application.dsn#")#</cfoutput>
</span>


The lexicon also turned out to be very handy for managing the text to display within links. We have a seperate table containing links with generic descriptions, then utilizing the GUID of the link record as the termID, created lexicon entries to control what link text and language was displayed for a given client.

Pretty straightforward, eh?

Doug out.

Disclaimer: The code and samples in this post have been written as simply as possible in order to illustrate "how" the lexicon works. In actuality we utilized Reactor and Coldspring to operate our Lexicon. The reader is encouraged to use these snippets as a starting point, but by all means to come up with more efficient ways of execution, object setup, and architecture for his or her own purposes.
Posted by dougboude at 11:37 AM | PRINT THIS POST! | Link | 1 comment
02 August 2007
Text-Link-Ads: leaving Adsense in the dust
Inspired by Michael Dinowitz' article on "Making Google Pay", I recently decided to follow his recipe and see if perhaps I too could make Google pay. I really had no expectations going into it, but I must say that I'm not overly impressed with the results. Over the course of about two months, Google has paid me a whopping [exact amount ommitted for fear of ticking off Google. Suffice it to say, it isn't much]. On top of that, the time it took me to dissect Michael's article, set myself up on Adsense, apply some of the more subtleties of Adsense (like adding Adsense-specific comments to portions of my site in order to help guide the types of ads that showed up), etc. was actually more than $9 worth of work. Yeah, it can be argued that "it's nine dollars more than you had!", true; and frankly all I have to do now that the work is done is just wait. However, in my frequent perusings I came across another company who also pays you to display a few relevant links on your site: Text-Link-Ads.com

Unlike Google's pay structure, where you are paid according to actual click-throughs, Text-Link-Ads pays you for hosting the ad itself. For instance, in the course of the first day after I registered with them, they sold an ad onto my site and promptly paid me $17. As I understand it, they are going to sell up to seven more ads at the same price, and I'll receive that same money every month.  I know, in either case (Google or Text-Link-Ads) it isn't a boatload of money; but when it took me two months and a few hours of work to earn $9 from Google, and it took me one day and about fifteen minutes of work to earn $17 from Text-Link-Ads, to me it's a no-brainer: Text-Link-Ads rocks! Besides that, there's no rule against having both side by side, and in fact, visually you can't tell much of a difference between them.

If anybody else is interested in trying out Text-Link-Ads, here are the basic steps:

1. Register with Text-Link-Ads (http://www.text-link-ads.com). You'll receive a key.
2. Add a wee bit o' code to your site to retrieve your ad inventory (here's the code I wrote to do that, just to help you get started):
<!--- Text Link Ads section --->
<cfset xPath = "//Link">
<cfhttp URL="http://www.text-link-ads.com/xml.php?inventory_key=[your key here]" DELIMITER="," RESOLVEURL="no" />
<cfif isXML(cfhttp.filecontent)>
    <cfset linkads = xmlSearch(xmlparse(cfhttp.filecontent),xpath)>
    <br>
    <cfloop from="1" to="#arraylen(linkads)#" index="i">
        <cfif linkads[i].Text.xmlText IS NOT "Test Link Ad">
            <CFOUTPUT>#linkads[i].BeforeText.xmlText# <a href="#linkads[i].URL.xmlText#">#linkads[i].Text.xmlText#</a> #linkads[i].AfterText.xmlText#</CFOUTPUT><br />
        <cfelse><!--- let's output the content into a comment so we can view it in the source, just to see what it was --->
            <!-- <CFOUTPUT>#linkads[i].BeforeText.xmlText# <a href="#linkads[i].URL.xmlText#">#linkads[i].Text.xmlText#</a> #linkads[i].AfterText.xmlText#</CFOUTPUT> -->
        </cfif>
    </cfloop>
    <br>
    <hr width="75%">
    <br>
<cfelse>
    <!-- file content was not xml... -->
</cfif>

3. Give Text-Link-Ads a little time to sell ads on your site!

For the first twelve hours or so, your xml will only contain a test ad. After 12 hours that goes away and any ads sold will be present. Whenever Text-Link-Ads sells an ad on your site, you'll get an email informing you of that fact so you can make sure it's showing up properly.

That's it. I've only been using this service for a few days now, but if any other relevant info manifests itself I'll be sure to share it.

Doug out.
Posted by dougboude at 10:38 AM | PRINT THIS POST! | Link | 9 comments
31 July 2007
Coder Funnies (well, I think they are!)
This is a photo blog, click here to view all thumbnails. or click here to view the text of this entry.

A while back my sense of self-entertainment took the form of cartooning and over the course of a week I drew up a few that I thought were funny. Of course, everybody has a different sense of humor, so what makes my diaphragm spasm may not affect you at all. If not, well, I can only speculate that there's something terribly handicapped about your sense of humor, and I sincerely hope at some point  that a Lourdes-level miracle occurs (for some, that's what it takes!) and you suddenly find it healed. Just kidding. I'm man enough to accept that I may be somewhat more warped than the average gnurd bird. Anywho, without further adieux, click the pic links at the top if you wanna see into my 'maginations....
Posted by dougboude at 12:26 AM | PRINT THIS POST! | Link | 1 comment
27 July 2007
Model Glue Views Demystified
At first, getting a grasp on how to "think" about rendered views in Model Glue may appear to be challenging; but I tell you, that you already know exactly how to think about them. If you have EVER included a template using <CFINCLUDE>, then you have a two minute learning curve to working with views in Model Glue.

Breaking your page up into individual pieces, and then including those pieces later on as needed is not a new thing for most of us. That is precisely what Model Glue allows us to do as well. Is it ever required that we divide our code into individual templates? Nay, and neither does Model Glue require it. It is, however, a very good idea in most instances, and MG gives us a very easy way to do this.

Picture if you will, a template.

We have dsp_main.cfm that will act as our layout, containing only DIV tags with appropriate IDs. Dsp_main.cfm also includes our external style sheet to provide appropriate positioning and look and feel to whatever content we put into those DIVs. Before Model Glue, we'd do it like this:

<html>
<head>
    <title>My Composite Template</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css" media="screen" />
</head>

<body>

<div id="header"><cfinclude template="dsp_header.cfm"></div>
<div id="nav"><cfinclude template="dsp_nav.cfm"></div>
<div id="body"><cfinclude template="dsp_body.cfm"></div>
<div id="footer"><cfinclude template="dsp_footer.cfm"></div>

</body>
</html>


Nothing new there, right? With Model Glue, the same dsp_main.cfm looks like this:

<!--- grab my rendered views out of the viewcollection... --->
<cfset header = viewCollection.getView("header") />
<cfset footer = viewCollection.getView("footer") />
<cfset body = viewCollection.getView("body") />
<cfset nav = viewCollection.getView("nav") />

<html>
<head>
    <title>MG View Demo</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css" media="screen" />
</head>

<body>

<div id="header"><cfoutput>#header#</cfoutput></div>
<div id="nav"><cfoutput>#nav#</cfoutput></div>
<div id="body"><cfoutput>#body#</cfoutput></div>
<div id="footer"><cfoutput>#footer#</cfoutput></div>

</body>
</html>


With Model Glue, rather than include those templates within the page itself, the templates were pre-rendered (almost exactly like using a CFSAVECONTENT), the rendered html stuffed into what's called the ViewCollection, and then we simply grab that rendered HTML out of the ViewCollection and output it in a very familiar fashion.

Now, how were those individual templates included? We told Model Glue to do it within the Modelglue.XML file that defines our individual events. For instance, let's say the event we called was 'main.landing' (http://www.mysite.com/index.cfm?event=main.landing). The relevant XML looks like this:

<event-handler name="main.landing">
    <broadcasts />
    <results />
    <views>
        <include name="body" template="dsp_content.cfm" /><!-- available in the viewcollection as 'body' -->
        <include name="nav" template="dsp_nav.cfm" /><!-- available in the viewcollection as 'nav' -->
        <include name="footer" template="dsp_footer.cfm" /><!-- available in the viewcollection as 'footer' -->
        <include name="header" template="dsp_header.cfm" /><!-- available in the viewcollection as 'header' -->
        <include name="main" template="dsp_main.cfm" /><!-- container template to layout individual parts -->
    </views>
</event-handler>


Using the 'include' tag within the 'views' tag, we told Modelglue which templates to render. Of note is the fact that the order in which they are rendered is completely irrelevant, WITH ONE EXCEPTION: the template that acts as the layout container MUST BE RENDERED LAST.

Also of note is the fact that each of your view templates should act independently of one another. For example, let's say that a Model Glue variable is needed in the dsp_nav.cfm and that same variable is also used somewhere in the dsp_content.cfm template. It would be bad practice for you to retrieve that variable within dsp_nav.cfm and then attempt to access the retrieved instance from dsp_content.cfm; each template should be retrieving it for themselves.

That's it, boys and girls. Nothing to it, and very little difference from the way you've been using <CFINCLUDE> all along!
Posted by dougboude at 12:01 PM | PRINT THIS POST! | Link | 4 comments
23 July 2007
Anti-Spam snippet
When I first began blogging, I was naive enough to think that porn bots (or whatever they're properly called) wouldn't find me. Wrong. So, I enabled the Captcha that comes built in to BlogCFM (yep, not CFC...I'm a rebel). That immediately thwarted their attempts at decorating my blog posts with colorful solicitations. Then a month or two later, I got a sudden influx of the same spam! So, I swapped out my captcha with something that would require some real thought: a math problem. That stopped them for another couple of months, then lo and behold it happened again. I really have no idea how the spam hackers do it, but it has resulted in yet another evolution in my efforts to stop the spam. So far it's worked solidly, so I thought I'd share it in case anybody else might find it useful.

It consists of a function that generates a question that must be answered, and the answer to that question. It will ask the commenter to figure out what letter is exactly X number of places before or after a randomly selected letter in the alphabet, then directs them to type their answer exactly Y number of times in the answer box. Upon page load the correct answer is saved to a persistent variable, then when the comment is submitted the answer typed (form.answer) is compared to the stored answer (session.answer).  Simple enough I think, but with enough randomness to make it something that can't be automatically breached without some real effort.

Here's the code for the function:

<cffunction access="public" name="genQuestion" output="false" returntype="struct" description="I generate a random question to use as an antispam key">
    <cfset var stReturn = structnew() />
    <cfset var firstnum = randrange(1,4) />
    <cfset var secondnum = randrange(1,4) />
    <cfset var letter = randrange(5,20) />
    <cfset var where = randrange(1,2) />
    <cfset var answer = "" />
    <cfset var i = "" />
    <cfset variables.numbers = "one,two,three,four" />
    <cfset variables.letters = "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z" />
    <cfset variables.beforeafter = "before,after" />   
    <CFSET stReturn.times = randrange(1,4) />
    <cfset stReturn.question = "What letter comes " & listgetat(variables.numbers,firstnum) & " places " & listgetat(variables.beforeafter,where) & " the letter " & listgetat(variables.letters,letter) & "?<br>Type your answer exactly " & listgetat(variables.numbers,secondnum) & " times in the box below." />
    <cfif where eq 1>
        <cfset letter = letter - firstnum />
    <cfelse>
        <cfset letter = letter + firstnum />
    </cfif>
    <cfloop index="i" from="1" to="#secondnum#" >
        <cfset answer = answer & listgetat(variables.letters,letter)/>
    </cfloop>
    <cfset stReturn.answer = answer />
    <cfreturn stReturn />
</cffunction>

(the function returns a structure containing the keys "question" and "answer")

Here's some starter code for utilizing it:

Code to evaluate saving a comment...
<cfif isDefined("saveComment")>
    <cfif form.answer neq session.answer>
        <cfset errorMessage = errorMessage & "<li>Invalid anti-spam key.  Please try again.</li>#Chr(10)#">
    <cfelse>
        <!--- perform comment saving here --->
    </cfif>
</cfif>


Code for displaying question and capturing answer:
<cfset variables.spamquestion = genQuestion()>

<h3>Please answer the following question:</h3>

<CFOUTPUT><STRONG>#variables.spamquestion.question#</STRONG></CFOUTPUT><br>
Type in the answer to the question you see above:
<input type="text" name="answer" size="6" maxlength="6" required="Yes" Message="You must complete the anti-spam field.">

<!--- save the answer to session for evaluation after the form is submitted... --->
<cfset session.answer = variables.spamquestion.answer>


Hope it helps!   :)
Posted by dougboude at 2:17 AM | PRINT THIS POST! | Link | 6 comments
05 July 2007
DEMYSTIFYING JSON (for myself)
I'm doing this post because the term 'JSON' has continued to appear here and there within blog posts, conference sessions, articles, and emails that I consume as part of my professional growth regimen. Despite the fact that the term is so very often mentioned casually as if everybody has known about it since Kindergarten, the greater part of my understanding of JSON is barren except for the few clues I have managed to glean through context. So, I decided to take the time to get to know JSON a little more intimately, and learned some interesting things.

WHAT I THOUGHT IT WAS
Based solely on the info I managed to gather from "between the lines", I knew that JSON stood for JavaScript Object Notation, and that it was an alternative to XML when dealing with the results of Ajax calls. Knowing that much, I could deduce that it was basically "data in a string". But what it looked like, how to handle it, and why I would want to do it were still questions in my mind.

WHAT I FOUND IT TO BE
JSON is indeed a string representation of simple or complex data, just as is XML, only without the tags AND without the need to treat that string as a document type in order to transverse it elegantly. JSON contains two indicators: curly braces to indicate that a structure follows, and square brackets to indicate an array. Here's a structure written in JSON: {key1:"val1",key2:"val2",key3:"val3"} and a one dimensional array: ["val1","val2","val3"]. You can nest these types within one another, too; for example, the value of a structure key could be an array, and would be written as this: {key1:["val1","val2","val3"],key2:"val2"}. Pretty cool, eh?

Now, how to get data back and forth between JSON and raw CF types. It's a no-brainer using a tag made available as an open source project on RIA Forge by Andrew Powell: CFJSON . I downloaded it and was using it in less than two minutes on a test template. Very simple, very easy.

All of this good information led me to pose more questions, which I went on to get answers to.

  • Were there any performance advantages of using JSON over XML?
  • Was there significant differences in the length of the string produced by a JSON conversion as opposed to an XML conversion (significant in the realm of web services where that string will have to be sent over the wire)?
  • Were there any compelling reasons to adopt JSON in leiu of XML or WDDX?

Following are the results of me finding out the answers to those questions.

THE EXPERIMENT
Working with a 500 row recordset, each row containing 52 fields of mixed data types, including some sql text, convert it to both JSON and XML and record the results of the time it took and the resulting string length. Do this in both a local template/cfc environment AND over the wire via a web service. I used CFJSON as the JSON converter, and Ray Camden's XML.cfc as the XML converter.

THE RESULTS
LOCAL TESTS
1
JSONXML
String Size 539906 1250561
Time (ms) 36374 2046
2
JSONXML
String Size 539906 1250561
Time (ms) 36156 2640
3
JSONXML
String Size 539906 1250561
Time (ms) 37103 2156

WEB SERVICE TESTS
1
JSONXML
String Size 539906 1250561
Time (ms) 36581 2390
2
JSONXML
String Size 539906 1250561
Time (ms) 37832 2496
3
JSONXML
String Size 539906 1250561
Time (ms) 37644 2406

The string size was consistent, as expected. JSON produced a string that was 57% smaller than the same data represented as XML. Even so, the actual difference in the time it takes to send the larger string doesn't even come close to compensating for the additional time it took to produce the shorter JSON string. It took 1,370% LONGER to produce the JSON string than it did the XML string. Wow, at this point it's almost a no-brainer that I would NOT want to use JSON for much if anything. But this huge difference got me to thinking: What was so vastly different about the way the XML was being produced and the way the JSON was being produced? So I dug into the CFCs to find out.

THE DIFFERENCE
Both CFC methods make use of lots and lots of looping and string concatenation. The only glaring difference was in the way this was done. XML.CFC leverages the Java.lang.stringbuffer object and CFJSON uses straight CF string manipulation. So, I altered the portion of the method in CFJSON.CFC that converts queries to utilize the java.lang.stringbuffer object as well. The results were VERY favorable! check out these time tests after I made the switch:

CFJSON.CFC TESTS AFTER STRINGBUFFER CONVERSION
TESTLOCAL TIME(ms)WEB SERVICE TIME(ms)
1 2156 4437
2 2125 3843
3 2031 4022

Wow. The conversion to JSON now runs neck in neck with the XML conversion, simply by leveraging the stringbuffer object. Sweeeeet.

Okay, now that I know that JSON data strings are significantly smaller, just as fast to create, how about manipulation? How painful is that? To find out I decided I was going to use my JSON data in a javascript function. I scaled down the size of the data set to only five records and a handful of fields for this test. Check out how simple it was to 'dump' my data into javascript and to access it afterwards:
<script>
    function showMeSomeJSON(){
        var objQuery = <CFOUTPUT>#jsondata#</CFOUTPUT>;
        var i = 0;
        for(i=0;i<objQuery.recordcount;++i){
            alert(objQuery.data.due_date[i]);
        }
    }
</script>


CONCLUSION
I have come to no solid conclusion as of yet, but I must say that I'm leaning heavily towards JSON in lieu of XML. It's got a smaller footprint all the way around, has the ability to capture complex nesting, and is really straightforward to navigate using Javascript. Additionally, the same way CFJSON can encode complex data, it also DECODES it on the receiving end, turning it back into something that CF recognizes.

Oh, and I think I'm going to email the CFJSON guys and let them know about the significant performance improvement I got when switching to stringbuffer. They may have already experimented in this arena, but just in case, I'll let them know.

That's it for my personal "JSON Demystification". :)

Doug out.

P.S. If you would like to see an actual JSON representation of my five record query, here it is:

{"recordcount":5,"columnlist":"complete,date_desired,dept,details,developer,due_date,est_hours","data":{"complete":["100%","100%","0%","100%","0%"],"date_desired":["Less Than 2 weeks","Less Than 1 week","More Than 2 weeks","Less Than 1 week","Less Than 1 week"],"dept":["Eligibility","Marketing","Benplan_com","Repricing","EDI"],"details":["1. For rehires, if a rehire date is present on the file and the rehire date is greater than the date of hire, then I need to use it.\r\n\r\n2. Cobra reason - make sure the term reason for a benefit is being put on the AE2 file. This is tied in with Scott\'s work.\r\n\r\n3. Some dependents have termed benefits but no effective dates, also no effective date for the employee. I need to find out where these are coming from.\r\n\r\n4. Terms need to be removed from the file 60 days after the employment term date. This will actually be added to the TSS AE2 file as well.","Dave Lawson, SWI, would like for us to send him the annual report we created for him that gives him EE and Dependent data for Group #s 90947 and 90947P.","- We will send?? or receive daily RX claims\/deductible file updates to one or two PBM\'s--Innoviant for sure and maybe Catalyst.\r\n"," 1) Show correct network for each group. 2) Change elapsed days to start counting on the day the claim was sent out. ","Correct the SSN validation on inbound EDI returned repriced claims. "],"developer":["Dan Crouch","Kelly Young","Gemma Anthony","Kelly Billen","Kelly Billen"],"due_date":["09\/09\/2004","8\/19\/04","NA","08\/30\/2004","08\/13\/2004"],"est_hours":["",1,400,1,1 ]}}

Posted by dougboude at 5:50 PM | PRINT THIS POST! | Link | 4 comments
02 July 2007
Basic Event Security in Model-Glue Applications
To anyone who has not yet breached the subject of model-glue event security, it can potentially be confusing at first, so I thought I'd share my approach to it in case it helps save someone a little time.

Understanding and being able to visualize the life-cycle of a Model-Glue event is a prerequisite to really grasping event security, so let me share my take on what a brief overview of that life-cycle is.

  1. A request is made, notifying model-glue to execute a specific event (eg; http://www.somesite.com/?event=fireinthehole )
  2. MG looks in its modelglue.xml file to find out what messages to broadcast to listening controllers ( <broadcast><message name="blowitup" /></broadcast> ) for the 'fireinthehole' event
  3. controllers listening for the "blowitup" message execute their corresponding functions ( <controller name="bombController" type="controller.eodGuy"><message-listener message="blowitup" function="BlowInPlace" /></controller> )
  4. MG executes any relevant result actions (acts as an 'if' statement almost) if they exist
  5. MG renders any views that are defined for this event
  6. The event lifecycle is over.

So, now that we know the flow of a named event within Model-Glue, it's time to add in a security check to make sure the current user has permission to execute that event. In my scenarios typically I have private and public events (those that can be executed without being logged in (such as the 'login' event itself), and those that require previous authentication (such as 'manageAccount')) and events requiring a specific role (such as viewing billing reports).
What we will effectively do is slide in some functionality between the event request and the execution of the event itself by leveraging OnRequestStart. This functionality will either allow the named event to pass on through OR redirect the user to the event we want them to arrive at. For instance, if they attempt to access an event that requires login and we intercept that event, we'll redirect them to the login page.

Here's the process for putting the named event check functionality in place:

1.Create a function that checks the current event name against a given list of event names;
2.Register that function to be called at 'onRequestStart';
3.In the Modelglue.xml file, create an event called "modelglue.OnRequestStart";
4.Within the modelglue.OnRequestStart event, register named results and provide appropriate redirection values;

What's going to happen then during the event life-cycle is that, before the actual named event executes, any controllers listening for the 'onRequestStart' message will execute their functions. One of those functions will have the sole job of verifying that the event being requested is allowed to be called in the current session state (logged in, not logged in, is an admin, etc.) If the event is good to go, the function is finished. If the event should NOT be allowed to execute, the function will set a model-glue named result. Next, since we defined an event called modelglue.OnRequestStart, that event is evaluated before any named events. The only thing we have defined for it to do is to look for specific named results and if one of them is found, perform the appropriate redirection. If none of the results being watched for are present, the named event executes normally.

I know at this point there must be a lot of questions on how to actually implement what I've been describing at a high level, so here are the same steps with snippets you can use:

1.Create a function that checks the current event name against a given list of event names;
In our scenario, let's assume two things: that we have events we want to require security, and events that we want to require that the person logged in also be an administrator.
<CFFUNCTION name="checkEvent" access="public" returntype="void" output="false">
    <CFARGUMENT name="event" type="any">
    <CFSET var eventname = arguments.event.getValue(arguments.event.getValue("eventValue")) />
    <CFSET var user = arguments.event.getValue("currentUserObject") />
    <CFIF not user.getUserID() and not listFindNoCase("login,signup,forgotpassword,sendpassword", eventname)>
        <!--- if we aren't logged in AND the event we're calling is NOT a public event... --->
        <CFSET arguments.event.addResult("LoginNeeded") />
    <cfelse><!--- we are logged in. If this event is in our list of events requiring admin login, check to see our user is an admin. if not, redirect them to home. --->
        <cfif listFindNoCase("admin.home,admin.billing,admin.creditAccount", eventname)
                and not user.getRole("Admin")>
                <CFSET arguments.event.addResult("AdminNeeded") />
        </cfif>
    </CFIF>
</CFFUNCTION>


2.Register that function to be called at 'onRequestStart';
<controller name="MyController" type="controller.Controller">
    <message-listener message="OnRequestStart" function="checkEvent" />
</controller>

3.In the Modelglue.xml file, create an event called "modelglue.OnRequestStart";
and
4.Within the modelglue.OnRequestStart event, register named results and provide appropriate redirection values;
<event-handler name="modelglue.OnRequestStart">
    <results>
        <result name="LoginNeeded" do="login" redirect="true" />
        <result name="AdminNeeded" do="home" redirect="true" />
    </results>
</event-handler>

As with anything Model-Glue or OO, there's always greater levels of detail to be expounded upon, but these snippets are more for illustrative purposes than actual 'out of the box' code, so I'm leaving a lot of the details to you. Such as where you should really store your lists of named events, or how your user object is always present, empty or populated, regardless of whether or not the user is authenticated, etc.

Anyway, hope this helps get somebody over the hump!
Posted by dougboude at 3:27 PM | PRINT THIS POST! | Link | 2 comments
20 June 2007
Circular Dependency Experiment
Circular Dependency is a phrase used to describe two objects who both use one another internally. That's simple enough. But...how the heck do those two OBJs get inside each other? And how do they behave once they're in there? These were some of the questions I needed answers to, and so I'd like to share with you my experiment and some of the things it taught me about circular object dependency.

I basically had the following questions I wanted to answer:

  1. How can I successfully get instantiated objects inside one another?
  2. Once instantiated, does object 2's manipulation of its internal copy of object 1 affect object 1's state, and vica versa?

Getting Objects Inside One Another
Okay, so how do we get an object inside another object? We inject it (embrace the phrase, you're gonna hear it a lot in the OO world!). With Coldfusion CFCs, this can be done one of two ways that I'm aware of: using "Setter Injection", or "Constructor Argument Injection". The Coldspring framework does that very thing for us when we leverage it in our apps, BUT I wanted to know what the raw, manual process of doing so looked like, so I created two sets of rudimentary CFCs, each of which has circular dependencies within them.

The first set (CFC1 and CFC2) have dependencies which utilize setter injection; The second set (CFCA and CFCB) use constructor arguments to get inside each other. CFC1 and CFC2 are identical, code-wise, as are CFCA and CFCB. Here is what they look like:
CFC1 and 2:
<cfcomponent displayname="CFC1 and CFC2">
    <cffunction name="init" access="public" returntype="any">
        <cfreturn this>
    </cffunction>
    <cffunction name="getMyName" access="public" returntype="string">
        <cfreturn variables._myname />
    </cffunction>
    <cffunction name="setMyName" access="public" returntype="void">
        <cfargument name="newname" type="string" required="true" />
        <cfset variables._myname = arguments.newname />
    </cffunction>   
    <cffunction name="getObj2" access="public" returntype="any">
        <cfreturn variables._obj2 />
    </cffunction>
    <cffunction name="setObj2" access="public" returntype="void">
        <cfargument name="obj2" type="any" required="true" />
        <cfset variables._obj2 = arguments.obj2 />
    </cffunction>
</cfcomponent>

(The Setter Injection method, as seen in CFC1, simply utilizes a "setObj2" method to set an internal variables-scoped variable to hold an instantiated copy of CFC2)


CFCA and B:
<cfcomponent displayname="CFCA and CFCB">
    <cffunction name="init" access="public" returntype="any">
        <cfargument name="obj2" type="any" required="true" />
        <cfargument name="myName" type="string" required="true" />
        <cfset variables._myName = arguments.myName />
        <cfset variables._obj2 = arguments.obj2 />
        <cfreturn this>
    </cffunction>
    <cffunction name="getMyName" access="public" returntype="string">
        <cfreturn variables._myname />
    </cffunction>
    <cffunction name="setMyName" access="public" returntype="void">
        <cfargument name="newname" type="string" required="true" />
        <cfset variables._myname = arguments.newname />
    </cffunction>   
    <cffunction name="getObj2" access="public" returntype="any">
        <cfreturn variables._obj2 />
    </cffunction>
    <cffunction name="setObj2" access="public" returntype="void">
        <cfargument name="obj2" type="any" required="true" />
        <cfset variables._obj2 = arguments.obj2 />
    </cffunction>
</cfcomponent>

(Notice that I still do have setters and getters for obj2; I didn't really have to have a setter, but the getter is needed so I can retrieve obj2 and call its methods)

Here is the code I used to instantiate CFC1 and CFC2 using the Setter Injection method:
<cfscript>
    //circular dependency using setter injection...
    obj1 = createobject("component","CFC1").init();
    obj2 = createobject("component","CFC2").init();
    obj1.setMyName(newname="Object 1");
    obj1.setObj2(obj2);
    obj2.setMyName(newname="Object 2");
    obj2.setObj2(obj1);
</cfscript>

Here is the code I used to instantiate CFCA and CFCB using the constructor argument method:
<cfscript>
    //circular dependency using constructor argument injection
    objA = createobject("component","CFCA");
    objB = createobject("component","CFCB");
    objA.init(obj2 = objB.init(obj2 = objA,myName="object B"),myName="object A");
</cfscript>

Okay, everything instantiated without any errors! Cool, and I have answered my first question. Now for question 2...

Object Behavior
How do these objects behave when set up in this fashion? To find out, I conducted interviews with each of the objects. Following are some exerpts and code snippets from those interviews (full interview can be seen here):

Interviewing obj1...

Q: What is your name?
A: My name is Object 1!
My name is #obj1.getMyName()#!
Q: What is your dependent object's name?
A: My dependent object's name is Object 2!
My dependent object's name is #obj1.getObj2().getMyName()#!

Interviewing obj2...

Q: What is your name?
A: My name is Object 2!
My name is #obj2.getMyName()#!

Q: What is your dependent object's name?
A: My dependent object's name is Object 1!
My dependent object's name is #obj2.getObj2().getMyName()#!

Q: Can you change your dependent object's name?
A: I don't know...let's give it a try, shall we?
(lots of grunting and straining sounds can be heard...)
<cfscript>
    obj2.getObj2().setMyName(newname="Eduardo");
</cfscript>

I think I did it! My dependent object's name is NOW Eduardo!
I think I did it! My dependent object's name is NOW #obj2.getObj2().getMyName()#!

Interviewing obj1 again...

Q: What is your name NOW, obj1?
A: My name is Eduardo!
My name is #obj1.getMyName()#!

Q: Why don't you go ahead and try to change YOUR dependent object's name...
A: Sure thing! My dependent object's new name is Florence. He he he he he.
<cfscript>
    obj1.getObj2().setMyName(newname="Florence");
</cfscript>
My dependent object's new name is #obj1.getObj2().getMyName()#. He he he he he.

Interviewing obj2 again...

Q: Obj2, is that really your new name?
A: Lemme check... Florence...yep!
Lemme check...#obj2.getMyName()#...yep!

I conducted the same interview with objA and objB, and the answers were identical.

My Conclusion
Circular dependencies, in my experiment, are nothing more than providing each object with a proper internal reference to the instance of the other object, in a reciprocal fashion. The fact that I could change the value of a variables-scoped variable in an injected instance of obj2, and that change be visible by obj2 itself from outside of obj1 proves that point unequivocably. Just for fun, I asked one final question of Obj1:

One Final Deep Question for obj1...

Q: Obj1, what is the name of your dependent object's dependent object's dependent object's dependent object?
A: It's name is Florence
It's name is #obj1.getObj2().getObj2().getObj2().getMyName()#


Cool stuff. Remember though, Coldspring is there for you to manage all of this so that you don't HAVE to do it manually. Coldspring can perform even deeper work, too, like injecting individual METHODS into objects on the fly! That's a little thing I like to call Aspect Oriented Programming, or AOP. You should take the time to investigate it.

Doug out.
Posted by dougboude at 2:26 PM | PRINT THIS POST! | Link | 3 comments
Just What IS Circular Dependency?
Circular dependency.

Initially, the phrase invokes visions of things that we naturally know to steer clear of, like infinite loops. If we've come to OO from a procedural world, we've probably been groomed to fear circular dependency in coding, reinforced by having encountered errors that demeaningly declared "Can't resolve circular dependencies, Idiot!". Circular dependency...it just sounds dangerous, doesn't it? Unstable, unpredictable, uncontrollable; without an end and without a beginning, as in the proverbial question of the chicken and the egg. In OO though, circular dependency needn't be a taboo phrase at all.

When you take a look at the natural world (of which Object Oriented methodologies are a rough emulation of), circular dependencies are common occurrences, and actually viewed as good things. I need my job and my job needs me. That's circular. I need my spousal unit and my spousal unit needs me. That's got a roundness to it, too (pun intended).  But, even deeper than the "we need each other" view, circular dependency implies that each of the two are within one another. Kinda like the ol' Frampton song, "I'm in you....you're in meEEeeeee....". A circular dependency in code is exactly the same thing: two objects who depend on one another for something and exist within each other. My user service needs my address service, and my address service needs my user service. We shouldn't be looking for ways to break these guys up! Rather, let us look at their relationship with admiration, because it is a perfectly natural one.

Now, some architects (we're ALL architects to some degree) might hold fast to a rule of "No Circular Dependencies", and rather than have two objects dependent upon one another directly, they'll create a third object to act as the broker between them. Instead of the two objects being injected into one another, those same two objects are instead injected into the third object, who orchestrates how they work together. I wouldn't say this is a bad thing, and in fact probably makes perfect sense to do so in many situations. Bottom line, though, is that we don't have to avoid circular dependency relationships when they do make sense.

All this talk about circular dependencies got me to wondering just how it all works under the covers. For instance, if I instantiate a CFC that needs another CFC internally, how can I do that without having first instantiating the OTHER CFC (who itself needs an instance of the first CFC I haven't instantiated yet!)? And if I DO manage to get instances injected into one another, will they be by ref or by val? In other words, will object 1 have a copy of object 2 inside of it, and my object 2 have a copy of object 1 inside of it? Or, will they both actually be seeing one another real time? Time for an experiment in object dependency! I'll share the results very soon.

Doug out.

P.S. Here is the link to my circular dependency experiment. (http://www.dougboude.com/blog/1/2007/06/Circular-Dependency-Experiment.cfm).  It REALLY helped clear some things up regarding how to "think about" this topic.
Posted by dougboude at 1:33 PM | PRINT THIS POST! | Link | 1 comment
25 May 2007
Custom Validation with Generic Commit: a Model-Glue Case Study
Someone recently asked about how to specifically ensure that a submitted email address is unique when using a model-glue generic commit, so I thought I'd share an example since I recently had to do that very thing.

I'm assuming for the remainder of this post that the reader is already familiar with Model-Glue in a practical sense, and at least knows of the existence of Reactor's automagic validation. Still, I'll try not to leave out too many relevant details.

Okay, the scenario:

I have a secured app, and I want to give new users the opportunity to sign up for an account. I'm using email address as the user name since in theory it should always be unique to an individual. (note: I opted NOT to set up my user table so that the email address field has a unique index on it) So, the user clicks "Sign me up!", I present them with a form to fill out, one field being their email address. They submit the form, and here's where we dive down under the waves to see what's happening...


The form submits to the event "inspector.create", which in the modelglue.xml file reads as follows:
<event-handler name="inspector.create">
    <broadcasts>
        <message name="ModelGlue.genericCommit">
            <argument name="recordName" value="UserRecord" />
            <argument name="criteria" value="" />
            <argument name="object" value="User" />
            <argument name="validationName" value="UserValidation" />
        </message>
    </broadcasts>
    <views></views>
    <results>
        <result name="commit" do="inspector.newuser" redirect="true" append="email" preserveState="false" />
        <result name="validationError" do="inspector.signup" redirect="false" append="" preserveState="true" />
    </results>
</event-handler>

Notice we're using a generic commit to handle this, which works because all of the needed data resides within form fields that are named exactly as their database field counterparts (eg; in my table there's a field called 'email', in my form there's a form field named 'email', etc.).
Now, the fact that we have specified an argument named "validationName" in our generic commit means that before the form data is committed to the database, Reactor is going to invoke the aid of one of the CFCs it auto-generated for us in order to "validate" the info submitted. By default, validation consists of checks that were created based on your table's metadata (unique indexes, datatypes, null not allowed, etc.), but Reactor was kind enough to provide us a convenient place to extend and customize that default validation if we so desire. In my scenario, since I did NOT choose to put a rule in place specifying that my email field should be unique, I added a custom method to perform that check.

To locate the CFC for adding custom validation, look in \model\data\reactor\Validator\, and find the cfc named after your target table. In my case, it's UserValidator.cfc

By default, the guts of the Validator CFCs you'll be working with look similar to the following:

<cfcomponent hint="I am the validator object for the Section object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
    extends="reactor.project.myprojectname.Validator.SectionValidator">
    <!--- Place custom code here, it will not be overwritten --->
</cfcomponent>

I decided to add two more methods of validation to my user object:
  1. make sure they typed their password in twice the same way,
  2. and make sure their email isn't already used in the system
Ah, but when you look at the content of my CFC, you'll see there are THREE methods present, and NOT just two. Take a gander:

<cfcomponent hint="I am the validator object for the User object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
    extends="reactor.project.myprojectname.Validator.UserValidator">

    <CFFUNCTION name="validate" access="public" hint="I validate an  record" output="false" returntype="any" _returntype="reactor.util.ErrorCollection">
        <cfargument name="UserRecord" hint="I am the Record to validate." required="no" type="any" _type="reactor.project.housefacks.Record.UserRecord" />
        <cfargument name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="any" _type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <CFSET validatePasswordVals(arguments.UserRecord, arguments.ErrorCollection) />
        <CFSET validateEmail(arguments.UserRecord, arguments.ErrorCollection) />
        <CFSET super.validate(arguments.UserRecord, arguments.ErrorCollection) />
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
   
    <CFFUNCTION name="validatePasswordVals" access="public"  output="false" returntype="reactor.util.ErrorCollection">
        <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" />
        <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <!--Blue is only allowed as a selection for Sky for people  whose weathercode = 5-->
        <CFIF  arguments.UserRecord.getPassword() is not arguments.UserRecord.getConfirmPassword() >
            <CFSET arguments.ErrorCollection.addError("user.password.notconfirmed") />
        </CFIF>
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
   
    <CFFUNCTION name="validateEmail" access="public"  output="false" returntype="reactor.util.ErrorCollection">
        <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" />
        <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <cfset var userGateway = "" />
        <CFIF  arguments.UserRecord.getEmail() is not "" >
            <cfset userGateway = reactorfactory.createGateway("user").getByFields(email=arguments.UserRecord.getEmail()) />
            <cfif userGateway.recordcount IS NOT 0>
                <CFSET arguments.ErrorCollection.addError("user.email.alreadyexists") />
            </cfif>
        </CFIF>
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
</cfcomponent>


The method present here that you may not have anticipated is called "validate", and is the exact same name as the method you would find in Reactor's core user validation object. So, what have we in effect done, boys and girls? That's right! We have (choose your favorite word, they both mean the same thing) overloaded/overriden the main "validate" method, in order to ensure that not only the original, auto-generated validation methods get called, but also the two new ones we added after the fact.

Let's take a closer look at our version of the "validate" method (this will only take a second, there are a couple of important things to note in there).

First off, you'll note that every validation method requires two arguments: an incoming record whose values are being validated, and the errorcollection where we (dang, this makes too much sense!) collect our errors.
Second, notice that we are FIRST executing our custom validation methods, then afterwards executing the auto-generated "validate" method by calling the object we extended, directly, via a call to "SUPER". Very cool, eh? Even though we initially overloaded our validate method in order to ensure that it got called rather than the core version of it, we were STILL able to call the original version as well. (By the way, that is a little trick I learned from Doug Sims' blog www.evenamonkey.com).

Alright then, we have submitted our form, used generic commit to perform validation, that validation called our extended object, executed the local custom methods first, then the system validate method. If any errors were encountered, our generic commit would have added a result named "ValidationError" to the event bucket (see the modelglue.xml snippet above), thus redirecting us back to the original page (where we have code in place looking for the presence of the error collection). If no errors were encountered, we're directed forward to the next event in the chain, and all is well.


One Mo Thang

Ah, one last thing that is of great importance to be aware of regarding Reactor validation: The Dictionary. The dictionary is an xml file that is specific to a validation object. In our example, since we have a userValidator object, there also exists a \model\data\reactor\Dictionary\userdictionary.xml file. This file is used to look up and translate any errors encountered so that the user is presented with readable text rather than a cryptic message. When you add custom validation methods, you also need to add dictionary entries. Consider the following snippet from my userdictionary.xml file:
<?xml version="1.0" encoding="UTF-8"?>
    <User>
        <email>
            <label>email</label>
            <comment/>
            <maxlength>100</maxlength>
            <scale>0</scale>
            <invalidType>The email field does not contain valid data.  This field must be a string value.</invalidType>
            <invalidLength>The email field is too long.  This field must be no more than 100 bytes long.</invalidLength>
            <notProvided>The email field is required but was not provided.</notProvided>
            <alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists>
        </email>
        <password>
            <label>password</label>
            <comment/>
            <maxlength>50</maxlength>
            <scale>0</scale>
            <invalidType>The password field does not contain valid data.  This field must be a string value.</invalidType>
            <invalidLength>The password field is too long.  This field must be no more than 50 bytes long.</invalidLength>
            <notProvided>The password field is required but was not provided.</notProvided>
            <notconfirmed>The password you typed in is not the same as the "confirm password" value.</notconfirmed>
        </password>
    </User>

Look back at the custom method "validateEmail" we added earlier, and notice that if our validation fails, we're adding an error that looks like

<CFSET arguments.ErrorCollection.addError("user.email.alreadyexists") />


The syntax of that message is no coincidence...it's the same syntax you would use to access an item in an XML file. Fancy that! 'user' denotes the user dictionary; 'email' denotes the particular table field; and 'alreadyexists' is a term I just made up, and indicates that Reactor should look for a tag called 'alreadyexists' in order to find the correct translation for this error. Thus, you'll notice the tag

<alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists>

in the <email> section of our dictionary file.


To Sum it all up!

Okay, so in a nutshell, if you have a form being submitted and you want to ensure that the email address is unique (AND you haven't put a rule in place within the database itself so specifying this):

  1. add a method to your customizable validator CFC for the target table;
  2. add a 'validate' method to the same CFC in order to overload the system version of the same;
  3. within your custom 'validate' method, execute your custom method first
  4. within the same, execute the system version of validate using "SUPER.Validate()"
  5. edit your dictionary file to add a translation for your new custom error

That's it!

Doug out.
Posted by dougboude at 1:30 AM | PRINT THIS POST! | Link | 9 comments
22 March 2007
Just What IS the Factory Pattern, Anyway?
A Factory makes things, right? And that is precisely what is being referred to when someone talks about a Factory, or the Factory Pattern: an object or framework whose job it is to create other objects for you.
So, rather than create your own object yourself like so:

<cfset myObject = createobject("component",model.myObject)>

 
You would call upon your Factory object to make it for you, similar to the following:

<cfset myObject = myFactoryObject.MakeMeAnObject(obj = “model.myObject”,args = stArgs) >

 
If you opt to have an object create your other objects for you, thenYOU, O Best Beloved, are a user of the Factory Pattern, and may boast of it in public settings as the mood strikes you.

Here’s a question that’s BEGGING to be asked: WHY would anybody wanna add that level of complexity to their app???

I’ll have to respond to that question with another question: Has it crossed your mind that sometimes, perhaps, one object might need an instance of another object inside of itself in order to do some kind of work?

Whether it has or hasn’t crossed your mind, the fact is that many times this will be the case, especially when you’re writing your app around an MVC framework. Now, the traditional method of making one object available within another object would be to simply write a line of code within your CFC that instantiates the other object, such as in our example above. However, what if you are using this same CFC…we’ll say, a CFC that performs emailing duties, within many other objects? Then one day you make a change to the Email.sendMail() method that requires an additional parameter be passed in. You would have to go to every other CFC that instantiates the Email.CFC and modify that line of code to accommodate the new parameter. Could take a long time, you might miss one, etc. Using a Factory, however (such as the Coldspring framework, which I am totally in love with), you can make that change in one place and the Factory will ensure the change is cascaded appropriately.

So what would be a real world scenario where I would actually need one object inside of another? Hmmm…how about registering a new user for access to a site? The steps involved with registering a new user are:

  1. create the user record
  2. send the user an email.

In order to keep my functionality all segregated nice and neat so i can use it here and there, I have myself a User object that handles user records, and an Email object that performs emailing duties. Since my registration process means I need to be able to perform functionality from both the User object AND the email object, I create a third object called RegistrationService.CFC that will do nothing more than orchestrate, or coordinate, work that the User and Email objects know how to do. This scenario is just one of many compelling reasons to utilize a factory.

(FYI, that coordination of work is what makes RegistrationService a service layer object...not the fact that it has 'Service' in the name.)

One last thought...Factory Pattern…this is another one of those “patterns” that I do not consider to be a pattern of any sort. From my real world experience, an object can BE a Factory, or you could even create an entire application (or framework) whose job it is to BE a Factory…but a pattern of factories? Nah, doesn’t gel.

Doug out.

Posted by dougboude at 5:16 PM | PRINT THIS POST! | Link | 1 comment
Just What IS the Singleton Pattern, Anyway?

You, O Best Beloved, are a Singleton. There is nobody else like you in the whole wide world, nor has there ever been. You’re a singular occurrence, a unique random combination of genetics that exists only wherever you happen to be at a given moment. And if any of  your friends wants to interact with you, they know just where to find you. You can interact with lots of different people, but no matter how many friends you may have, every one of them is interacting with the exact same YOU. When your good friend Dave asks you to tell Theresa hello next time you see her, you store that information and next time you interact with Theresa, you pass along that exact message.

 
You getting the picture here? If you instantiate a CFC and put it in a place (Application scope, anyone?) where EVERY other part of your application is able to interact with THAT PARTICULAR instance of your CFC, then YOU, O Best Beloved, have created your object as a Singleton. Now, for whatever reason, people seem to be awfully fond of tossing around the buzz phrase “Singleton Pattern”…not sure why. It’s way simpler just to use the word as an adjective describing how you have instantiated your object, and it makes more sense, too.

 A few other thoughts on Singletons…
 

  • It’s vital to understand this term because it has a huge effect on how your app will work. Basically, if an object is created as a Singleton and you aren’t able to visualize what that means, you could have people seeing other people’s data. Not good.
  • That I’m aware of, there is no opposite of Singleton...an object either IS a Singleton, or it isn't.

 
POP QUIZ!

Which of the lines of code below is creating my object as a Singleton?

A) <cfset adminObject = createobject("component",model.admin) >

B) <cfset application.adminObject = createobject("component",model.admin) >

Posted by dougboude at 4:41 PM | PRINT THIS POST! | Link | 13 comments
19 March 2007
Using Ajax with Model-Glue - it's really quite simple
At first glance, the topic can seem somewhat overwhelming. The key, however, is to understand each of them separately and then using them together won't seem overwhelming in the least. I'll touch briefly on each of them independently, but will assume for the bulk of this post that you have at least an elementary understanding of what each is and generally how they work.

Ajax refers to the ability we have to make Javascript run back to our web server and retrieve information for us, without reloading the entire page. It is doing nothing more than making an http call to a web page, just like we would do everytime we click a link on a page. The Javascript, however, makes the call in the background, unseen by the user, receives whatever content results from that web page call, and then does something with it inside of our current page.


Model-Glue is a popular Coldfusion application framework that is becoming the "procedural man's tour guide to object oriented programming", breaking "the rest of us" into the world of OO. If you haven't played with it or invested the time to get familiar with it, you have a lot of your peers who HIGHLY recommend it. I recently had the privilege of doing
a presentation for the Kansas City ColdFusion User's Group on Model-Glue that was saved as a Breeze preso if you need or want some kind of intro.

Okay, from this point forward I can safely assume that you have at least a working understanding of Ajax and Model-Glue as separate tools, and will now share with you how the two integrate easily (from the 1,000 foot view). I will NOT be focusing on any one Ajax library, as there are several good ones out there, and as the specific Ajax library being used is (or should be, if the Ajax library is intuitive enough) COMPLETELY IRRELEVANT to understanding how they integrate with one another.

First thing, consider a basic Model-Glue event lifecycle:

1. A Model-Glue event is called via navigation to a URL (index.cfm?event=app.home).
2. The Model-Glue framework looks up the event (in the modelglue.xml file), determines what broadcasts to make, what results to evaluate, and what views to render.
3. Rendered views are returned to the browser.
4. Browser displayes the rendered view.

Hmm, nothing complex here, eh?

Now consider a basic Ajax call to a Model-Glue event:
1. A Model-Glue event is called via Javascript library (var newcontent = ajax.remotecall('index.cfm?event=ajax.getInfo');).
2. The Model-Glue framework looks up the event (in the modelglue.xml file), determines what broadcasts to make, what results to evaluate, and what views to render.
3. Rendered views are returned to the browser.
4. Javascript receives the rendered view and does whatever it was told to do with it (document.getElementById('ajaxDiv').innerHTML = newcontent;)!!

So what is different when incorporating Ajax into the mix? Very, very little. The only REAL difference that I've found I need to keep in mind is in regard to what my view contains when the event I'm calling was specifically created for an Ajax call. For instance, in a "normal" event call, i'm typically going to be returning an entire html page, complete with body tags and the works. In an Ajax call, it will be either a chunk of html or some kind of Javascript-ready data (XML, JSON data, etc.) I have found that it's simpler, whenever possible, to return a chunk of pre-rendered HTML and have my client-side javascript simply place the returned html into the target div. Working with returned XML is where Ajax will become more complex, but even then, the complexity is STILL on the client side JS coding and not anything to do with Model-Glue at all.

I don't believe that there's anything else a person needs to know in order to be able to "think about" and visualize the interaction between Model-Glue and Ajax...it really is straightforward stuff! Not sure why just a few months ago it sounded like such a difficult concept to me....

Here's an illustration showing how Ajax would work with a Model-Glue page:



The actual Javascript that would handle the call could be as simple as the following example:
<!--- First, we load up the ajax library... --->
<script src="/js/AjaxLibrary.js" type="text/javascript"></script>

<!--- Now define the function we'll be using when the user clicks our "Ajaxified" link... --->
<script>         
function gotClicked(){
    /*first, retrieve the new content ... */
    var newcontent = ajax.remotecall('index.cfm?event=ajax.getInfo');
    /*now stuff the newly retrieved content into our ajaxDiv... */
    document.getElementById('ajaxDiv').innerHTML = newcontent;
}
</script>

That's it, really! Nothing more to it. After grasping this very basic understanding, the only thing left to do now is to select an Ajax library (personally, I take a shine to Scriptaculous...) and start learning how to use it!

Doug out.

Posted by dougboude at 11:34 PM | PRINT THIS POST! | Link | 6 comments
05 March 2007
Coldspring Automagic Lesson
Okay, I spent the greater part of today at work wrestling with a situation involving the injection of objects into other objects using Coldspring. After far too many hours of experimentation, furrowed brow, taking up my teammate's time, and frustration, I came to some conclusions. I can't explain the "WHY's" of the conclusions, but i can definately validate the conclusions themselves by my results.

Here's the scenario:

I have object B which extends object A. Object A has an INIT method, object B does not. Object B is being injected into object C. In my Coldspring.xml, I define a bean for object A, B, and C. Object A has a property value ("thisURL") being set via a <property> and <value> tag; object C has object B injected via a <property> and <ref> tag.  (see illustration and coldspring.xml snippet)

The Challenge (there are no problems, only challenges): When all objects are instantiated, I am calling method Y on object C. Method Y itself refers to a method X in the injected object B. Method X in injected object B refers to the property "thisURL" that was set via Coldspring within object A. WE GET AN ERROR, HOWEVER, INDICATING THAT THE VALUE IN OBJECT A WAS NEVER SET!

The Experimentation:
Hmmm, let's put a CFABORT tag in the "setThisURL" method in object A (the method needed by Coldspring in order to 'auto-inject'), with a 'ShowError' attribute. The abort is never executed, meaning that Coldspring never called the 'setThisURL' method on object A, despite the fact that we configured it to do so.

Okay, let's move everything we need into object B, remove the "Extends" attribute from the cfcomponent tag, re-do our Coldspring.XML to inject the 'thisURL' property directly into object B. Hey, it works!

Let's move it all back again, and try adding an init method to object B that performs a "Super.init()" to see if that fixes it. NOPE.

In my mind, in the mind of my peers, configuring Object A to have a property value injected should have resulted in Coldspring executing the "setThisURL" method. In fact, I NEED it to do that, because I plan on utilzing this base class (object A) by extending it with other objects as well, and the constant 'thisURL' needs to be present and the same in all cases. However, it did not behave in the expected manner.

As I stated at the beginning, I have NO idea why it did not behave as I expected. But, I thought I'd share the results of my headache in case it helps someone else avoid one later.

Conclusion:
The only way to make this work was to have Coldspring set the 'thisURL' property directly in object B if I wanted to be able to access it after having extended object A.

Illustration and Coldspring.xml snippet:


Snippet:
<bean id="objectA" class="model.objectA" >
    <property name="thisURL">
          <value>http://my.testserver.com/myWS.cfc?wsdl</value>
    </property>
</bean>

<bean id="
objectB" class="model.objectB" />

<bean id="
objectC" class="model.objectC" >
     <property name="objectB">
         <ref bean="objectB" />
    </property>   
</bean>

(back to top)
Posted by dougboude at 12:00 AM | PRINT THIS POST! | Link | 11 comments
02 March 2007
Dynamic Data Stores: A Model-Glue/OO Case Study
The scenario is that my team and I are building an app that must be able to seamlessly communicate between multiple datastores; one local, and the remainder remote and accessible via web service calls. In order to accommodate this, I've created a DataStoreAdapter component that has an instance of each of the specific data store interface components injected into it by Coldspring.

The challenge here was to find a way to dynamically call the appropriate methods on the appropriate data store object. After several different attempts, following is what I came up with that works (you may want to first sneak a peek at the visual I added to this post):


1. My user object (nestled within my user service layer) calls a generic method (named appropriately "callMethod") on the DataStore Adapter:

<cfset stResults = getUserDataStoreAdapter().callMethod(argumentCollection=arguments) />

(note: one of the arguments being passed in within the argumentcollection is 'methodname' with a value of 'getUserData')

2. The 'callMethod' method in the datastore adapter dynamically invokes the appropriate generic method:
    <cffunction ACCESS="public" name="callMethod" OUTPUT="false" RETURNTYPE="any" DESCRIPTION="I am the generic method that calls the appropriate backend system.">
        <cfargument name="backendSystem" TYPE="string" REQUIRED="yes" HINT="I am the name of the targeted backend system" />
        <cfargument name="methodName" TYPE="string" REQUIRED="yes" HINT="I am the name of the method to call within the targeted backend system" />
        <cfset var local = structnew() />
       
        <cfinvoke argumentcollection = "#arguments#" method = "#arguments.methodName#" returnvariable = "local.myResults" />
       
        <cfreturn local.myResults />
    </cffunction>

(arguments.method name in this case = "getUserData")

3. The generic method that was called invokes the appropriate specific method to return the autowired object needed, then the appropriate method is called from the specific backend object returned by the invoke:
    <cffunction access="private" name="getUserData" OUTPUT="false" returntype="any" DESCRIPTION="I am the generic getUserData function" >
        <!--- incoming argument collection is implied, no need to specify individual arguments. --->
        <cfset var local = structnew() />
       
        <!--- step 1: Make the call to the appropriate backend system... --->
        <cfinvoke  method = "get#arguments.backendSystem#User" returnvariable = "local.myObject" />

        <!--- we're invoking the backend-specific method from the appropriate object in the following line... --->
        <cfreturn local.myObject.getUserData(argumentcollection=arguments) />
    </cffunction>

(notice this specific method is private...can only be invoked by the "callMethod" method...)

4. The returned data is passed back up the calling chain to the original calling component.

Here's a diagram that might help to visualize it better.

Back to Top
All data massaging is encapsulated in the lowest level backend system components, so that each of them will return a consistent data set regardless of what they themselves are receiving from the backend system. And, as our upper management dictates that we interface with yet ANOTHER wack backend system, all we'll need to do is create the low level component and wire it in to our datastore adapter service!

Anyway, I was pretty pleased with how we addressed this situation so thought I'd share it.

Doug out.
Posted by dougboude at 4:35 PM | PRINT THIS POST! | Link | 1 comment
Sweet Little Snippet: Query to Arguments
I have a CFC method with one argument that is an incoming, single-row query:

<cfargument NAME="remoteData" TYPE="query" REQUIRED="yes" DISPLAYNAME="remoteData" HINT="I am the query returned (and formatted) from a remote data store." />

This method will call another internal method X in order to perform some work, but method X will be looking for the query data as arguments (necessary because method X is also used by other methods who will pass in discrete argument values).

So, I need to "convert" my 'remoteData' argument to actual argument values before I call the other method.


<!--- add our remoteData values to the arguments... --->
<cfloop list = "#arguments.remoteData.columnlist#" index="f">
     <cfset arguments[f] = remoteData[f][1] />
</cfloop>
<cfset myResults = methodX(argumentcollection = arguments) />
<cfreturn myResults />


Pretty simple, but not necessarily so straightforward when having to treat queries as a structure of arrays, so thought I'd share it.

As an aside, I went ahead and toyed with converting an entire query to a structure dynamically, too. Following is the snippet I used:


<!--- create a query to play with using querysim... --->
<cf_querysim>
UserInfo
userID,firstName,lastName,userGroups
100|Stan|Cox|33
200|Joe|Blow|35
</cf_querysim>

<cfset stArgs = structnew() />

<!--- put the query to a structure... --->
<cfoutput query="UserInfo">
    <cfset thisKey = "Record" & UserInfo.currentrow />
    <cfloop list = "#UserInfo.columnlist#" index="f">
        <cfset stArgs[thisKey][f] = UserInfo[f][UserInfo.currentrow] />
    </cfloop>
</cfoutput>

<cfdump var="#UserInfo#">
<cfdump var="#stArgs#" />

Pretty cool. Here are the dumps:




Doug out.
Posted by dougboude at 11:13 AM | PRINT THIS POST! | Link | 2 comments
21 February 2007
Just What IS a 'Service Layer', Anyway?

(Consider hiring Doug to instruct you and your team in the ways of OO!)

I've been hearing the term 'Service Layer' everywhere for at least the past year and a half, as I'm sure most of my peers have as well. Whether it's seen in blog posts, heard during casual conversation at conferences, found in articles, or woven throughout almost any discussion at all concerning OO and Coldfusion...the term is quite prevalent and common these days. But I had a problem: the term held absolutely ZERO connotation in my mind. No picture was ever conjured up when I came across it, no corresponding 'Doug Boude' translation was found in my internal lexicon; I felt quite out of the loop. Ah, but recently that has all changed, as the phrase itself has not only come to life for me, but found an absolute and permanent place in all that I do, technically speaking; an epiphany, if you will.  I just figured there were others out there who, like myself, may still be kinda grasping for a way to think about the term, so what follows is my personal definition/explanation of what a Service Layer really is. Please feel free to append, addend, flip, and twist it to your heart's content until it's as whole as it ever can be.

SERVICE LAYER

 

 

This is not NEARLY as gray and ambiguous a term as you might think. Picture if you will, a man sitting comfortably on his sofa. In one hand is the remote for his very large plasma TV; in the other hand is a remote for his home theater system. The two remotes and the man are all objects, and all three come pre-built with things they can do. In the kitchen is the man’s wife; let’s think of her as the calling application. She barks out the order to the husband object, “START THE MOVIE, YOU IMBECILE!”. The husband object just happens to have a startTheMovie method, and begins to execute it. First, he manipulates the objTVRemote object, calling its “tvOn” method. Then, he manipulates the objDVDRemote object, calling it’s “dvdOn” and “dvdPlay” methods. Now he manipulates the objTVRemote again, calling the “inputSRC” method and switching the tv to receive the dvd input. Tada! Movie is playing now!

Pretty clear scenario, eh? Well, in this illustration, the MAN is acting as the SERVICE LAYER. Although he has a “startTheMovie” method, all he’s really doing is coordinating efforts between other lower level objects that actually do the work. His wife doesn’t care about the remotes or how they work, and her life is then simplified because she need only make her one call to her SERVICE LAYER OBJECT and he handles the nitty gritty details. Service Layer…not such a deep, complicated mystery after all, is it?

Doug out.

Posted by dougboude at 6:15 AM | PRINT THIS POST! | Link | 13 comments
18 September 2006
Client-Side Interactivity without Ajax
Keeping response times down and interactivity high has and always will be two important priorities with web interfaces of any kind. For standard html interfaces, Ajax is all the buzz and is great when it’s necessary to maintain interaction with live data. But when a static version of the data will do just fine, there’s at least one other alternative that you may want to consider….

The <CFWDDX> tag provides a way for us to bridge the gap between dynamic data processed on the server side (specifically, queries returned by CFQUERY), and Javascript on the client side. What this does is give us the ability to transform query results into a form that we can manipulate via javascript, opening up some very useful potential for interactive Dynamic HTML.

In a nutshell, the process goes like this:
  • A CFQUERY tag is run;
  • The result is fed to CFWDDX with the ACTION attribute set to CFML2JS, which produces a Javascript-ready recordset;
  • The JS function that works with the recordset is created
That’s it. Now, for some of the nitty gritty details

In my example, I’m displaying a list of employees for the user to select from. What I want though is that whenever an employee is selected from the list, their detailed information is displayed to the side so that the user can determine if this is the right person or not. Like so:

(go 'head! click it!)

 

Ajax would work for this! Everytime an employee’s name is clicked on, we could make a call back to the server and grab that employee’s detailed information, then, using Javascript, output it to the designated portions of the template without ever reloading the page (yet still executing an HTTP call over the web). Or, we could just go ahead and retrieve the employees’ details during our initial query, since we had to retrieve their names and IDs anyway in order to produce the select list.
<!--- Using QuerySim to create the cfquery... --->
<cf_querysim>
    UserInfo
    userid,fname,lname,ext,title,dept,email
    200|Joe|Blow|2235|Supervisor|Reporting|jblow@nowhere.com
    300|Doug|Boude|2112|WaterBoy|Recreation|dboude@nowhere.com
    100|Stan|Cox|1225|Developer|IT|scox@nowhere.com
    400|Jim|Pickering|1513|Designer|IT|jpickering@nowhere.com
    500|John|Smith|1112|CEO|Management|JASmith@nowhere.com
    600|John|Smith|1123|Janitor|Building Maintenance|JLSmith@nowhere.com
</cf_querysim>

We could then take that query and create a Javascript-friendly version of it like so:
<script>
    <cfwddx action="cfml2js" input="#UserInfo#" toplevelvariable="users">
....
</script>
(Note that the attribute “TopLevelVariable” contains the value that will be used to name our recordset; that is the name it will be referenced as within our JS function.)

Within the body of our page, we’ll go ahead and create the select list just like we always do, only let’s add a function call to the onClick event that we can use to dynamically populate the targeted portions of our template:
<select onClick="getUserInfo(this.value);" size="8">
    <option value="" selected></option>
    <cfoutput query="userInfo">
        <option value="#userid#">#lname#, #fname#</option>
    </cfoutput>
</select> 

Additionally, let’s create the placeholders for our employee details:
<table>
    <tr>
        <td><STRONG>First Name:</STRONG></td><td><span id="fname"></span></td>
    </tr>
    <tr>
        <td><STRONG>Last Name:</STRONG></td><td><span id="lname"></span></td>
    </tr>
    <tr>
        <td><STRONG>Title:</STRONG></td><td><span id="title"></span></td>
    </tr>
    <tr>
        <td><STRONG>Department:</STRONG></td><td><span id="dept"></span></td>
    </tr>
    <tr>
        <td><STRONG>Extension:</STRONG></td><td><span id="ext"></span></td>
    </tr>
    <tr>
        <td><STRONG>Email:</STRONG></td><td><a id="email" href=""></a></td>
    </tr>
</table>
(Note: In my example, I use SPAN tags as placeholders as well as an Anchor tag, but any valid document object (form fields, div tags, etc.) can be targeted as placeholders, as long as they have a unique ID value)

At this point, our query has been converted into a ‘WDDXRecordset’ object. Behind the scenes, it’s a complex Javascript array, but the nice folks at Adobe wrapped it up for us with a set of functions that make it easy to get at the data using JS.  For example, to retrieve the first name from the first row, the call would look something like
Var thisName = getField(0,’fname’);
....
(Note that our first row is referenced with zero instead of one.)

Now all we need is a function to retrieve the employee details. It’s as simple as a Javascript FOR loop, leveraging the functions provided by the WDDXRecordset object:
function getUserInfo(userid){
    var i = 0;
    for (i=0; i < users.getRowCount(); i++){// loop through our wddx recordset...
        if(users.getField(i,'userid') == userid){//if the current record matches the ID passed in...
            document.getElementById('fname').innerHTML = users.getField(i,'fname');
            document.getElementById('lname').innerHTML = users.getField(i,'lname');
            document.getElementById('title').innerHTML = users.getField(i,'TITLE');
            document.getElementById('dept').innerHTML = users.getField(i,'dept');
            document.getElementById('ext').innerHTML = users.getField(i,'ext');
            document.getElementById('email').href = 'mailto:' + users.getField(i,'email');
            document.getElementById('email').innerHTML = users.getField(i,'email');
            return false; //ends this function call
        }
    }
    //if we made it here, then we found no match in our loop above; just blank everything out.
    document.getElementById('fname').innerHTML = "";
    document.getElementById('lname').innerHTML = "";
    document.getElementById('title').innerHTML = "";
    document.getElementById('dept').innerHTML = "";
    document.getElementById('ext').innerHTML = "";
    document.getElementById('email').href = "";
    document.getElementById('email').innerHTML = "";
}  
(Note: Although our function only utilized the getField() and getRecordCount() methods,  several more are available to us. You can take a gander at what else the WDDXRecordset provides at this LiveDocs link)

You can grab the entire template here if you'd like to see it all together.

Gotchas/things to remember:
  • some web hosts don't provide access to the CFIDE/Scripts directory. If this is the case, then you'll need to grab a copy of wddx.js and put it in your webroot, then reference it within a script tag prior to creating any wddx-powered functions, like so:
<script src="wddx.js"></script>//< - - - making sure wddx.js is loaded...
<script>
    <cfwddx action="cfml2js" input="#UserInfo#" toplevelvariable="users">
    function getUserInfo(userid){
        ...(remainder of js)
   
  • When referencing query fields, the js is not case sensitive (a getField(1,'title') will work as well as getField(1,'TITLE') ); However, javascript itself IS case sensitive, so referencing the target span tag you must use the exact case of the ID (getElementById('title') is NOT the same as getElementById('TITLE') )
  • The WDDXRecordset object contains a zero-based array, meaning that the index number of the first row will actually be referenced as zero rather than one (getField(0,’fname’) retrieves the value for first name from the first record)
That's it!

Doug out 
Posted by dougboude at 7:49 PM | PRINT THIS POST! | Link | 3 comments
08 September 2006
Database-Oriented Document Management
Nearly every time the question of storing documents or images in a database comes up, the answer is almost always "don't do it", and for very good reasons. There are those times, however, when it's very much the appropriate thing to do. In my case at my day job, we have multiple production servers that are maintained by another (very protective) team, and the method of load balancing in place is NOT conducive to keeping server content in sync. Therefore, when we want to post a new document or image on the web, it may very well be hours before said file is actually replicated to all production servers. In order to make an image or document immediately available, we decided that storing them within the database would resolve our challenge. Since the final solution did involve some research and a few fiery hoops, I figured I'd share it with the rest of the Ether  in case it might come in handy for someone else. Following is the general process and code snippets involved with making this happen.

Basic Process:
  • File is selected via a form within a document admin utility;
  • Selected file is read into binary and stored in a "Documents" table
  • Links to stored documents/images contain the UUID of the target document. Table is queried for that UUID, binary is retrieved from database.
  • File's binary is provided to a CFCONTENT tag, along with the mime type, and result is delivered to requestor.
Before we go any farther, please pay heed to these three items:

 1. All of the following code and snapshots are in regard to CFMX 6.1 and higher. The process described below is similar for CF 5, but there are some additional caveats and steps involved (Email me for particulars if you're still using CF5).

 2. You MUST allow the retrieval of BLOBs within your ColdFusion datasource where your document table lives, as in the following snapshot:
coldfusion dsn admin

3. Below is the script needed to create the sample table in MSSQL. The table used to store the documents should have a field of type IMAGE (if using MSSQL) in order to store binary data directly. If your database doesn't support a field type for binary data, then you'll need to create a field of type TEXT or its equivalent. Any binary data needing to be stored in a TEXT field will have to be BinaryEncoded/BinaryDecoded using Base64 as the second parameter in order to store and display it in this manner.

SQL Script:
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[docs]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[docs]
GO

CREATE TABLE [dbo].[docs] (
    [id]  uniqueidentifier ROWGUIDCOL  NOT NULL ,
    [filename] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [extension] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [filesize] [bigint] NULL ,
    [uploadedDate] [datetime] NULL ,
    [mimetype] [varchar] (75) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [mybinaryblob] [image] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[docs] WITH NOCHECK ADD
    CONSTRAINT [DF_docs_id] DEFAULT (newid()) FOR [id]
GO

On with the actual code then! Here are the details/snippets:

File selection
File selection is done via a form field of type "File", as in
<input type="file" name="myfile" size="50" />
<!---  --->
<!---  --->

Important: In the FORM tag, it is required that you set the ENCTYPE equal to "multipart/form-data", as in
<form action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post" enctype= "multipart/form-data">
<!---  --->
<!---  --->

Form Action code
Step 1 when processing a file upload is to UPLOAD it via CFFILE. This takes the file and places it on the server in a temporary directory designated by the webserver itself. This is also needful so that we can grab the file's metadata (file size, mime type, etc.).  The code to upload the submitted file looks like this:
<cffile action="UPLOAD" filefield="form.myfile" destination="#getDirectoryFromPath(getCurrentTemplatePath())#" nameconflict="OVERWRITE">
<!---  --->
<!---  --->

"form.myfile" is the NAME of the form field that was of type "File"; it should be given to CFFILE without pound signs. If we were to place form.myfile in pound signs (#form.myfile#), the UPLOAD process would be unable to locate the target file, so make sure you don't do that.

Once the file has been uploaded, we need to capture its metadata, which exists immediately after the CFFILE call in a structure called none other than CFFILE. Fancy that.
file metadata
CFDUMP of File Metadata

The recommendation is to capture a good portion of that information and store it along with the file's binary. Here we'll capture it to a structure for use later with the insert query, like so...
         <cfscript>
            stats = structnew();
            stats.ext = CFFILE.ClientFileExt;
            stats.name = CFFILE.ClientFileName;
            stats.subtype = CFFILE.ContentSubType;
            stats.type = CFFILE.ContentType;
            stats.size = CFFILE.FileSize;
        </cfscript>

Step 2 is to read in the binary of the uploaded file, like this:
<cffile action="readbinary" file="#form.myfile#" variable="vBin">
<!---  --->
<!---  --->

Notice in this case we aren't just providing cffile with the name of the form variable, we're giving it the actual path to the uploaded file (wrapping in pound signs).

At this point, we're ready to insert our file into the database.

<cfquery name="insertfile" datasource="#dsn#">
    insert into docs (filename,extension,filesize,uploadedDate,mimetype,mybinaryblob) VALUES (
    <cfqueryparam value="#stats.name#" cfsqltype="cf_sql_longvarchar" />,
    <cfqueryparam value="#stats.ext#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#stats.size#" cfsqltype="cf_sql_bigint" />,
    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp" />,
    <cfqueryparam value="#stats.type#/#stats.subtype#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#vBin#" cfsqltype="cf_sql_blob" />
    )
</cfquery>

That's it for storage!

RETRIEVAL and DISPLAY
Retrieval is pretty straightforward, too. We're going to retrieve the particular record we're interested in from the database

<cfquery name="getFile" datasource="#dsn#">
    select id,filename,extension,mimetype,mybinaryblob from docs where id = <cfqueryparam value="#form.getfile#" cfsqltype="cf_sql_char" maxlength="36">
</cfquery>

and then feed the retrieved binary and mimetype to a cfcontent tag

<cfcontent type="#getFile.mimetype#" variable="#getfile.mybinaryblob#" reset="No"  >
<!---  --->
<!---  --->


A word on stored images
Images are treated a little differently primarily due to the fact that in most instances you don't just want the page's entire content to consist of one picture, but rather have it retrieved and displayed inline within an <IMG > tag. The only variation in this case is that you have to create an Image Display template that does exactly what the example above states, and use that template as your IMG source, like so:

<IMG SRC="myImageDisplayTemplate.cfm?imageID=11111">
<!---  --->
<!---  --->

(Ben has a more detailed post on the topic of retrieving and displaying images from a database, if you're interested)

That's pretty much it. I've boiled our document management system down as far as I am able, but you can grab this code HERE if you'd like to use it as a starter for a system of your own.

Here is the entire self-posting Form:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<cfparam name="mode" default="" /><!--- parameter indicating whether we're asking for a file, storing a file, or retrieving a file --->
<cfparam name="dsn" default="test"><!--- datasource name to be used in this template --->

<!--- ********************************** UPLOAD A FILE ************************************************** --->

<cfif mode is "upload">
<!--- process new file... --->
        <!--- first, upload it to the server --->
        <cffile action="UPLOAD" filefield="form.myfile" destination="#getDirectoryFromPath(getCurrentTemplatePath())#" nameconflict="OVERWRITE">
         <!--- Now, before we exectute another CFFILE call, grab the uploaded file's metadata for later use --->
        <cfscript>
            stats = structnew();
            stats.ext = CFFILE.ClientFileExt;
            stats.name = CFFILE.ClientFileName;
            stats.subtype = CFFILE.ContentSubType;
            stats.type = CFFILE.ContentType;
            stats.size = CFFILE.FileSize;
        </cfscript>
        <!--- grab the binary version of the uploaded file --->
        <cffile action="readbinary" file="#form.myfile#" variable="vBin">
        <!--- insert it into the database --->
<cfquery name="insertfile" datasource="#dsn#">
    insert into docs (filename,extension,filesize,uploadedDate,mimetype,mybinaryblob) VALUES (
    <cfqueryparam value="#stats.name#" cfsqltype="cf_sql_longvarchar" />,
    <cfqueryparam value="#stats.ext#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#stats.size#" cfsqltype="cf_sql_bigint" />,
    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp" />,
    <cfqueryparam value="#stats.type#/#stats.subtype#" cfsqltype="cf_sql_varchar" />,
    <cfqueryparam value="#vBin#" cfsqltype="cf_sql_blob" />
    )
</cfquery>
        <cfoutput><a href="#cgi.script_name#"?mode="">Do It Again</a></cfoutput>
        <!--- dump this file's metadata --->
        <cfdump var="#stats#">
       
<!--- *********************************** Retrieve a Stored File *************************************** --->   
   
<cfelseif mode is "retrieve"><!--- we're in 'retrieve a file' mode...' --->
        <cfquery name="getFile" datasource="#dsn#">
            select id,filename,extension,mimetype,mybinaryblob from docs where id = <cfqueryparam value="#form.getfile#" cfsqltype="cf_sql_char" maxlength="36">
        </cfquery>
        <!--- this is all there is to it to display a file stored as binary --->
        <cfcontent type="#getFile.mimetype#" variable="#getfile.mybinaryblob#" reset="No"  >
       
<!--- ************************************ Let the User Select a File for Upload or Retrieval ********** --->   
       
<cfelse><!--- show the user interface so they can upload a new file --->
    <cfquery name="getFiles" datasource="#dsn#">
        select id,filename,extension from docs
    </cfquery>
    <form action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post" enctype= "multipart/form-data">
        <input type="file" name="myfile" size="50" />
        <input type="hidden" name="mode" value="upload" />
        <input type="submit" value="upload" />
    </form>
        <hr>   
    <form  action="<CFOUTPUT>#cgi.script_name#</CFOUTPUT>" method="post">
        <select name="getfile">
            <cfoutput query="getFiles">
                <option value="#id#">#filename#.#extension#</option>
            </cfoutput>
        </select>
        <input type="hidden" name="mode" value="retrieve">
        <input type="submit" value="Retrieve File">
    </form>
</cfif>

</body>
</html>
Posted by dougboude at 4:57 AM | PRINT THIS POST! | Link | 10 comments
01 September 2006
DeoxyRiboNucleic LISTS (DNL)
I love lists. Although they *seem* to take a backseat to their apparently more elegant "collection brethren" Structure and Array, to me Coldfusion lists have a hidden potential rivaled only by that ultimate List of all Lists, DNA. Four individual components used to create patterns within patterns within patterns, and within those patterns the blueprints for an entire species and everything about it...it's just amazing to me. I have personally used Coldfusion lists in similar fashion (though much less glorious) and so wanted to share a little of that passion and my reasons for it, in the hopes that others too (if they don't already) might be able to appreciate the beauty of a List as I do.

Here's a scenario:

I'm tasked with writing a component to perform e-commerce transactions against one of several different (and as yet unknown) merchant systems. I do know that there are only a handful of transaction types that can be performed (Sale, Pre-Authorization, Capture, Void, or Credit), and that along with each of these transaction types there will be some arguments that are required and some that are not. I also know that each of these arguments will need some additional validation before being submitted to the merchant system, and every argument's validation needs could potentially be different. On top of that, because of the fact that we'll be adding in new merchant systems all the time, it's entirely possible that a whole new set of validation rules could be needed tomorrow in addition to what's already in existence. And to top it all off, because the number of arguments could change overnight, I'm going to rely on the receipt of an argumentCollection rather than hard code my arguments via CFARGUMENT.

I actually did have this scenario a couple of years back, and although there are always many approaches that can be taken to accomplishing a task, I chose to utilize a List to hold my argument validation rules. Why a list? Because since I was a contractor, I knew I wouldn't be around the next time the rules changed, and being the nice guy that I am, wanted to make it as simple as possible for the person who would come after me. By encoding my validation rules for each merchant system within a List (just like a strand of DNA) and by receiving my arguments via a collection, the next programmer only needed to edit one line of an entire CFC in order to make their changes.

Here is an example of validation rules encoded within a List:

<cfset var validationRules = "TransactionType~Always~L|S-A-C-V-CR,CCNum~S-A-V-CR~CC,ExpDate~S-A-V-CR~D,Amount~S-A-CR~N|0,RefNumber~C-V-CR~T|7" />
<!--- --->
<!--- --->


In essence, just like DNA, it is a series of nested lists. Here is the key for deciphering the List:

  • Outer list: [Argument and associated rules],[Argument and associated rules], etc. delimiter ","
    • Argument and associated rules: [Arg Name]~[required when]~[validation] delimiter "~"
      • Required when: [Always||S||A||C||V||CR] delimiter "-"
      • validation: [validation type | validation param] delimeter "|"
        • validation type: [L||T||N||D||CC]
        • validation param: [[list of values]||[integer]]

In the sample list then, we can see for example that the argument "TransactionType" is always required and its value should be any of "S","A","C","V",or "CR".  The argument RefNumber is only required when the transactiontype is "C","V", or "CR", and when it is required should be a text value with length of at least 7 characters. You get the picture. SO then when our merchant system adds a new transaction type, such as "Reversal", we will add the letter "R" to represent this transaction type to any of the existing arguments that will be required for a Reversal. We'll then add any additional arguments with the appropriate values. All done in a single variable within the CFC.

What do we do with such a list? What beast is it that can consume it and actually use it? Well, You write yourself a method that loops through it and, one major list item at a time, interpret whatever rules have been defined and apply them. I've created a template and cfc for demonstrating this in action.

Step 1: template instantiates Payit.cfc, sets up an argument collection, and calls the "DoTransaction" method, passing in the arg structure.
<cfscript>
    argstruct = structnew();
    argstruct.TransactionType = "CR";
    argstruct.ccnum = "423339010000930";//cc number is only 15 characters...should be 16
    argstruct.expdate = "05/01/08";
    argstruct.amount = "35.67";
    argstruct.refnumber = "0098345";
    objPay = createobject("component","Payit").init();
    results = objPay.DoTransaction(argumentcollection=argstruct);
</cfscript>
<cfdump var="#results#" label="Results">

Step 2: "DoTransaction" first passes the incoming argument collection to the "validateArgs" method.
<cffunction name="DoTransaction" access="public" returntype="struct" output="false">
    <cfset var incomingArgs = arguments />
    <cfset var results = structnew() />
    <cfset results = validateArgs(incomingArgs) />
    <cfif results.error IS "[NONE]">
        <!--- Do a transaction... --->
    </cfif>
    <cfreturn results />
</cffunction>   

Step 3: "validateArgs" performs validation and passes a structure back to "DoTransaction" containing the results. (view the entire method at the bottom of this post)

Step 4: DoTransaction either performs its transaction, or it halts processing. In either case, it returns the validation structure to the calling page where it is output. In our example here, the credit card number being submitted was too short, and thus the following response:



Conclusion
Even though my example of using a list to encode detailed validation rules was very much geared towards an e-commerce example, I'm confident that one could find other uses of "ColdFusion DNA" by applying just a little bit of imagination and creativity. In any event, I recommend at least considering a list rather than automatically assuming a structure or array is the best fit every time.

Doug out.

[Download template and CFC]

validateArgs Method:
    <cffunction name="validateArgs" access="private" returntype="struct">
        <cfargument name="args" type="struct" required="true" />
        <cfset var validation = structnew() />
        <cfset var mode = "" />
        <cfset var currlist = "" />
        <cfset var validationRules = "TransactionType~Always~L|S-A-C-V-CR,CCNum~S-A-V-CR~CC,ExpDate~S-A-V-CR~D,Amount~S-A-CR~N|0,RefNumber~C-V-CR~T|7" />
        <cfset validation.success = "true" />
        <cfset validation.error = "[NONE]" />
        <!--- Since everything will always hinge upon the existence of the TransactionType argument, we check for it first thing, stopping if it isn't present.' --->
        <cfif not structkeyexists(args,"TransactionType")>
            <cfset validation.success = false />
            <cfset validation.error = "Transaction Type was required but not passed in" />
            <cfreturn validation />
        <cfelse>
            <!--- grab value of the transaction type. It may not be a valid value, but we'll find that out soon enough' --->
            <cfset mode = args.TransactionType />
            <!--- stuff our mode into the validation structure for troubleshooting purposes --->
            <cfset validation.mode = mode />
           
            <cfloop list="#validationRules#" index="i" delimiters=",">
                <!--- if this item is required for our current mode, make sure it exists... --->
                <cfif listgetat(i,2,"~") IS "Always"><!--- if this argument is always required, check for its existence --->
                    <cfif not structkeyexists(args,listfirst(i,"~"))>
                        <cfset validation.success =  "false" />
                        <cfset validation.error = listfirst(i,"~") & " was required but not passed in." />
                        <cfreturn validation />
                    </cfif>
                <cfelse><!--- our item is only required *sometimes*... --->
                    <cfif listgetat(i,2,"~") IS NOT "">
                        <cfif listfind(listgetat(i,2,"~"),mode,"-") neq 0><!--- if the current mode is in our list of required modes, make sure this arg is present... --->
                            <cfif not structkeyexists(args,listfirst(i,"~"))>
                                <cfset validation.success = "false" />
                                <cfset validation.error = listfirst(i,"~") & " was required but not passed in." />
                                <cfreturn validation />
                            </cfif>
                        </cfif>
                    </cfif>
                </cfif>
                <!--- if this item needs requires that the argument's value be validated, do so' --->
                <cfif listlen(i,"~") eq 3><!--- a third item indicates some validation needed --->
                    <cfset currlist = listgetat(i,3,"~") /><!--- grabbing the validation portion of our item --->
                    <cfif listfind(listgetat(i,2,"~"),mode,"-") neq 0><!--- if this was a required item in the first place... --->
                        <cfswitch expression="#listfirst(currlist,"|")#"><!--- perform the needed validation --->
                            <cfcase value="L"><!--- value must be in a specified list of values... --->
                                <cfif listfind(listlast(currlist,"|"),args[listfirst(i,"~")],"-") eq 0>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " contained an invalid value." />
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfcase value="T"><!--- value must be text of at least so many characters... --->
                                <cfif len(args[listfirst(i,"~")]) LT listlast(currlist,"|") >
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was not at least " & listlast(currlist,"|")  & " characters in length."  />
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfcase value="N"><!--- value must be a number greater than x... --->
                                <cfif not isnumeric(args[listfirst(i,"~")]) OR args[listfirst(i,"~")] lte listlast(currlist,"|")>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " is not a numeric value or is less than or equal to " & listlast(currlist,"|")/>
                                    <cfreturn validation />           
                                </cfif>
                            </cfcase>
                            <cfcase value="D">
                                <cfif not isDate(args[listfirst(i,"~")])>
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " is not a valid Date."/>
                                    <cfreturn validation />               
                                </cfif>
                            </cfcase>
                            <cfcase value="CC"><!--- value must be a credit card number ... --->
                                <cfif len(args[listfirst(i,"~")]) NEQ 16 ><!--- this test done purely for simulation. actual cc validation is more involved and complex. --->
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was not a valid credit card number."/>
                                    <cfreturn validation />   
                                </cfif>
                            </cfcase>
                            <cfdefaultcase><!--- the specified validation rule isn't accounted for! throw error' --->
                                    <cfset validation.success = "false" />
                                    <cfset validation.error = listfirst(i,"~") & " was given a validation code that was not recognized."/>
                                    <cfreturn validation />   
                            </cfdefaultcase>
                        </cfswitch>
                    </cfif>
                </cfif>
            </cfloop>       
        </cfif>
        <cfreturn validation />
    </cffunction>
Posted by dougboude at 4:32 PM | PRINT THIS POST! | Link | 0 comments
29 August 2006
Easy Security for Canvas Wiki
with auto-author population
I'm sure we here at my day job aren't the only ones who are using Ray Camden's Canvas Wiki and wish that those who author and edit pages within it didn't have to type their names in every time. Well leave it to my brilliant coworker Doug Sims to have implemented a slick, surgical way to incorporate both security and automatically capture the correct author/editor. The basic steps are:
  1. Enable IIS Challenge/Response on the web directory where Wiki lives;
  2. Modify one line in one file of the Wiki

Okay, if you have to do it yourself (and don't have a shining infrastructure department who manages your web servers), enabling security on the wiki directory is a snap.

  • Go to the IIS Admin, navigate to your wiki directory, right click and click Properties.
  • Click the Directory Security tab, then click the Edit button in the "Authentication and access control" section.
  • In the "Authentication Methods" window, uncheck the "Enable Anonymous acess" box and make sure that "Integrated Windows authentication IS checked.
  • Hit 'Okay', then 'Apply' and close out the IIS admin.
Screenshot of the IIS Admin:

Whenever a user navigates to the wiki web directory now, they will be asked for their username and password (it'll be the same one they use to log into their machine on the network). They may also have to append the network domain name to their username, as in the following example where user 'DBoude' is on the BPL domain: BPL\DBoude

For step 2, open the wiki template Views/dsp.edit.cfm. Somewhere around line 50 to 59, find the table row for author, and change it to this:

<tr>
    <td>
    Author:
    </td>
    <td><input type="text" name="author" value="#listlast(cgi.auth_user, "\")#"></td>
</tr>

That's it!

 

Posted by dougboude at 5:45 PM | PRINT THIS POST! | Link | 1 comment
28 August 2006
What is an 'Advanced' Coldfusion Developer?

 What is an “Advanced” ColdFusion developer?

 

Preface: You will see the word ‘framework’ used in this post once or twice, though it has little to do with the topic. Please refrain from using this word’s occurrence as the catalyst for yet another framework debate thread.

Thank You!

The Management


As we’re all well aware of, labels such as ‘Advanced’ are only a weak approximation at best used to give some context to an individual’s skill level and abilities. Shoot, for most of us, even our own job titles fit this definition, as there is no single phrase that totally encompasses our duties, abilities, and skill set. One reason it is that we humans take the time to factor a person or ourselves down into a single adjective or descriptive phrase is typically for convenience’s sake; it’s expected protocol that whenever we’re asked the question “what do you do?” or “How would you describe your current skill level?”, that we do it in a way that can be uttered with a single breath. Sometimes the whole labeling thing can also be misused as a self-serving item, fueling vices such as inordinate pride and the like; in that usage I personally condemn it, and make it a point not to use it in that way. For the remainder of this post, however, I’ll assume that those who seek the label that fits them best do so for reasons other than pride.

 
So regarding skill level assessment, what categorization would I ascribe to myself if asked to do so, and why? Using what criteria? Ah, even we as individuals in the privacy of our own minds would have to jump through a few hoops to arrive at an answer we felt really confident and comfortable with (assuming a healthy level of self-humility is present), so how on earth could we possibly do a better job of summing up someone else’s skill level? Between the lines of many different threads hither and yon regarding frameworks I see that the reason ‘Advanced’ and ‘frameworks’ and ‘OO’ are being spoken of in the same thought is the fear factor. I speak from experience, that at one point I felt, not from any internal source, but from the evolution of the community, a burning need to re-evaluate my own skill level. Why? Because in a nutshell, there were things going on out there that I did not know. That innate fear of the unknown that often has its subtle effects on the way we behave and think did just that, and caused me to wonder if by not forcing myself to “keep up with the Joneses” was the skill level status that I had personally settled on losing its actual meaning? I believe that everybody in our community, to one degree or another, felt it just like I did. Some said to themselves, “you know what, I just better go ahead and invest the time to traverse this new learning curve. It’s going to be painful and tedious probably, but I’d rather go through that pain than to face the potential situation of not getting a job because one of those new frameworks became an industry standard and I remained ignorant of it.” Still others, like I did for quite a while as well, said to themselves “Why should I question my skill level just because I don’t take the time to keep up with every single new thing that comes out? I shouldn’t, and I won’t.” But even having told themselves that, the fact that those new things persist and continue to become ever more prominent in the community keeps them questioning even when they don’t think they need to; it’s just human nature. And then every once in a while when someone inadvertently or even purposefully posts something that gives the slightest hint of equating OO or frameworks with Advanced, the need to defend their point of view is roused, and we see the myriad of debates such as we have. Let me be clear at this point, I am not belittling anybody regardless of which camp your personal philosophy resides in; the previous was merely my evaluation of what I believe regarding why it is there came to be two camps in the first place.

 
What I do believe to be relevant here is my philosophy on the very question of “Am I Advanced?”  You see, we all have our own personal hierarchy of skills; those that we have deemed as more or less advanced than another, and our own personal rules for classifying a skill somewhere in the junior to advanced range. With everybody harboring their own personal “opinion” on what is junior, what is mid-level, what is advanced, how can we even hope to answer the question of whether or not we as individuals fall into the “Advanced” category or not? In my opinion, to even pursue such a quest is an exercise in futility, with no reward of any consequence waiting for us at its conclusion. Even if we concluded that “yes, I am advanced”, so what? The next guy may not think so after applying his own hierarchy of skills to your resume. Does his opinion matter? To me, only if he’s my potential new boss would I care how he classified me according to my skills, and with that even, there’s no way I could know ahead of time what his personal hierarchy of skills is!. Other than the previous scenario, opinions are like armpits, right?

 
Bottom line is that classifying an individual’s skill set is a subjective thing at best, and is best done by ourselves for ourselves. We should not allow somebody else’s evaluation result to provoke us to anger or discourage us from going forward, but like any good student of any profession will, take any and all critique (including self-critique) and use it as a tool for progress. We’re the best judge of ourselves, and if we keep or pride in check and our minds open to new things, nature will take her course and we will continue to evolve and grow in a healthy, career-enhancing way.

 
So are you advanced? You tell me. Do you feel advanced? Are you aware of any areas in your personal toolbox where there are ambiguities or understandings that could stand to be refined? Areas in your professional experience that you haven’t really gotten your hands dirty in yet, dug down to the nuts and bolts so that you could get a thorough understanding of how a particular technique or tag behaves? Are you aware of new techniques or methodologies being explored and implemented by others in your professional community which you have yet to really take a good look at? The answer to all of those questions will likely always remain an unequivocal “absolutely dude!”. Does that mean nobody is ever advanced? Are you getting tired of reading the word “advanced” yet? You should be, because homing in on labels such as these are a bad thing to do unless it’s the appropriate thing to do at the time, such as during an interview and you’re asked point blank how you would categorize your skill level. When asked, give your honest estimation. Until then, I say that the question will probably be one best left as a reflexive one; ask it to yourself, of yourself, and when you do, let it be nothing more or less than a catalyst for your own personal and professional growth. But let’s not go the way of those poor, poor Sneetches who were more concerned with those who “had stars upon thars” and those who did not; it was and will always be an exercise in futility, not to mention a complete waste of the precious few moments we all have to invest in something better and more lasting in our lives.

 
Are you advanced? You be the judge of that. Ask yourself often, ask yourself honestly, and always strive to do better. Personally, I won’t look at you through “label colored glasses”. From where I’m standing, we are a community, a community with an enormous amount of collective experiences and knowledge, and I would venture to say that every single individual who has joined themselves to this community has nothing but the common good in mind, manifested by the very obvious and copious levels of sharing and assistance that takes place 24/7 from every side.

 
Are you able to wear the label ‘advanced’? I keep repeating the question so that I can beat it as the dead horse that it is, hopefully making it a distasteful question so that we won’t even have the stomach to utter it again anytime soon.  Fact is, it shouldn’t even be a question at this point; it’s almost completely irrelevant to anything we as a community work to accomplish. N’est-ce pas?

Posted by dougboude at 5:56 AM | PRINT THIS POST! | Link | 8 comments
25 August 2006
Maintaining Hierarchical Navigation Data
Storing hierarchical navigation data in a single table is a snap. Maintaining that data, however, can be a little bit of work (inserting sub items, removing sub items, etc.). What I wanted to share in this post is the solution i built for myself to perform navigation administration, where the navigation is stored in a single table with each record related to some other record in the same table. Bear in mind that I built it  for my eyes only, so isn't necessarily what I would give a customer to use. But, it didn't take an inordinate amount of time to build and it gets the job done, so I am kinda fond of it for those reasons. Without further adieux then, let's take a gander at it.

Following is a snapshot of the admin screen for maintaining the navigation. You'll notice it also incorporates a rudimentary sort of security.


So, in the scenario where I want to insert a sub-nav item somewhere, I simply add the new item's info to the "Add New" section at the bottom, making sure to designate its parent in the "Parent ID" column (choosing 'None' if this is a top level item). The sort order column is used to indicate where under that parent this particular item should show up. Right now, I have nothing in place to prevent me from having the same number twice, so I just have to look at the other children for this parent to determine what the sort order value should be.

Removing a nav item is as simple as checking the delete box. I have my update coded so that if a nav item is removed, its children are automatically removed as well.


Here are the relevant tables I use to store the navigation data (note that ev_nav has a relationship back to itself via an aliased instance of itself in the diagram):


If anybody is interested in getting a copy of the actual files (self-posting template, cfc, and mdb), feel free. Also, consider yourself warned, it's kinda fugly and wasn't meant for public review, but it should provide a good base for something prettier.
Posted by dougboude at 5:47 PM | PRINT THIS POST! | Link | 5 comments
24 August 2006
Removing FlexBuilder from Eclipse

Many of us have tried (or at least downloaded and installed) FlexBuilder, the trial version. For those of us who use Eclipse for other things besides Flex and never bothered to actually purchase FlexBuilder, we may find ourselves with a now expired plugin that tends to pop up now and then asking us to enter our serial number. No big deal really, only a minor annoyance. Except for today when I tried to open a CSS file.

I'm not even in the FlexBuilder perspective, but apparently the CSS extension had somehow been associated with FlexBuilder, so now instead of seeing my file open up in all its glory, I am prompted to purchase the product. I tell FlexBuilder "No thanks", but am unable to open my CSS file. So then, the point of this whole post: How to remove the FlexBuilder plugin from Eclipse.

Fortunately it's a very simple process.

  1. Close Eclipse
  2. Go to the "plugins" directory of your Eclipse installation
  3. Sort the Plugins directory entries by name so that all of the FlexBuilder items are together. Highlight them all and send them to Windows Purgatory.
  4. Now go to the "features" directory of your Eclipse installation
  5. highlight and delete all of the Flexbuilder entries there
  6. Restart Eclipse

All finished!
Posted by dougboude at 12:39 PM | PRINT THIS POST! | Link | 2 comments
Cool Eclipse Plugin for CSS and JS

Aptana is an Eclipse plugin that came to me highly recommended by a peer (Doug Sims) and has proven itself to be a real assett to me as well. What does it do? Only provide code assist with both Javascript and CSS files! Another VERY COOL thing it does within the code assist is provide visual indicators of which CSS and Javascript attributes are IE and/or Firefox compatible, something that is a constant plague to those trying to maintain cross-browser compatibility.

and now for something completely different...visit
its all MY effing fault!

Visit Aptana.com and click the "Download Aptana Plugin" link on the right hand side of the page for instructions on how to install.

One caveat of this plugin is that it's code assist feature is only available when working with .JS or .CSS files...inline css or javascript, well, you're on your own.

Posted by dougboude at 12:28 PM | PRINT THIS POST! | Link | 2 comments
When the Obvious is TOO Obvious: A lesson in Troubleshooting

Look at the following code and guess what the output would be:

<cfset strObj = createobject('component','com.makestring').init() />
<cfset newstring = strObj.getString() />
<cfoutput>#newstring#</cfoutput>

 

Oh, here's the cfc...

<CFCOMPONENT>
    <CFFUNCTION NAME="init" ACCESS="public" RETURNTYPE="makestring">
        <CFRETURN this>
    </CFFUNCTION>
    <CFFUNCTION
        NAME="getString"
        ACCESS="public"
        RETURNTYPE="string">
        <CFSET var thisString = "" />
        <CFSAVECONTENT
            VARIABLE="thisString">
            <ul>
                <li>list item 1</li>
                <li>list item 2</li>
                <li>list item 3</li>
            </ul>
        </CFSAVECONTENT>
        <CFRETURN thisString />
    </CFFUNCTION>
</CFCOMPONENT>

 

Choices:

  • A:[empty string]
  • B:
    • list item 1
    • list item 2
    • list item 3
  • C: depends

 

The correct answer is C, and what it depends on is whether or not somewhere else within your application a CFSETTING tag is lurking that has ENABLECFOUTPUTONLY set to 'Yes'. If this attribute is in effect, then your result will be A; if it's not, your result will be B.

The reason I felt the need to post on this is the fact that I spent a few hours yesterday developing a mild headache trying to figure out why my perfectly simple cfc was producing an empty string at one point within my app, but the correct string elsewhere. Through experimentation and noting that when I removed the CFSAVECONTENT tags everything worked as designed, I eventually concluded that I MUST have discovered a bug in the framework I was using, and so notified the keepers of the framework of all the details of my investigation and conclusion, along with code samples. They immediately (and nicely) informed me that more likely it was the fact that I had ENABLECFOUTPUTONLY enabled, and that I hadn't included CFOUTPUT tags within my CFC method. With egg still dripping from my face, I added the CFOUTPUT tags to my method and wouldn't ya know it...it worked.

I still haven't taken the time to find out where within this app the CFSETTING tag is being called, but two lessons gleaned here for me:

  1. Next time I feel excited at the prospect of having discovered a "bug", I'll think again (nobody wants the reputation of being The Boy Who Cried 'Bug!');
  2. Start troubleshooting from 10,000 feet instead of with a magnifying glass.
Posted by dougboude at 11:48 AM | PRINT THIS POST! | Link | 3 comments
18 August 2006
Dynamically accessing unknown query fields
Ever found yourself in a situation where you need to output values from a query, but you're unable to explicitly reference the columns by name? Perhaps the query is a "Select *", or the column names are being dynamically aliased within the sql based on the date. Whatever the reason, never fear, there are solutions to the challenge.

 

One's first instinct may be to call upon the trusty ol' Evaluate function, which will definately work. But what would your friends say if they saw you using a function that has such an evil reputation as a process hog? Here's at least one alternative way to output a query when you don't know the column names ahead of time.

The key lies in the fact that queries can also behave like structures. (they can also behave as one dimensional arrays, if the field names are known ahead of time). To leverage this behavior, we're going to use the column name as the first key and the rownumber as the second key. Like so:
<cfset fldname = "firstname">
<cfoutput>#myQuery[fldname][1]#</cfoutput>

For the query "myQuery", the value of "Firstname" in the first record would have been output.

In comparison, if we had known the field name ahead of time and wanted to grab that field's value from a specific record, we could have treated our query as a one dimensional array and referenced it like

<cfoutput>#myQuery.firstname[2]#</cfoutput>

outputting the value for firstname in the second record of the data set.

One final example

Following is an example that outputs a query in table format, without knowning anything about the query ahead of time
(We're using QuerySim to fabricate our data set. If you don't have a copy you can get it here.):
<cf_querysim>
    UserInfo
    userID,firstName,lastName,userGroups
    100|Stan|Cox|33
    200|Joe|Blow|35
    300|Doug|Boude|21
    400|Jim|Pickering|15
</cf_querysim>

<cfoutput>
<table>
    <tr>
        <cfloop
            list="#UserInfo.columnlist#"
            index="h">
            <th>#h#</th>
        </cfloop>
    </tr>
    <cfloop
        from="1"
        to="#UserInfo.recordcount#"
        index="i">
        <tr>
            <cfloop
                list="#UserInfo.columnlist#"
                index="c">
                <td>#UserInfo[c][i]#</td>
            </cfloop>
        </tr>
    </cfloop>
</table>
</cfoutput> 
Posted by dougboude at 4:23 PM | PRINT THIS POST! | Link | 1 comment
22 July 2006
CF tags within CFSCRIPT Blocks

Some of us are fans of using CFSCRIPT for certain portions of our code. For myself, I’ll typically create my UDFs (User Defined Functions) in this manner…it just looks cleaner to my eye. There can be challenges to doing this, however, due to the fact that certain commonly used CF tags just do not have equivalent CFSCRIPT function calls, like CFQUERY or CFTHROW. It is, however, still possible to incorporate them.

I think a code sample is worth a thousand words, so consider the following sample:

 

<!--- first, we need to turn the CFQUERY tag into a coldfusion function --->
<cffunction name="cfquery" access="public" returntype="query">
 <cfargument name="dsn" type="string" required="true">
 <cfargument name="sqlstring" type="string" required="true">
 <cfargument name="password" type="string" required="false" default="">
 <cfargument name="username" type="string" required="false" default="">
 <cfif arguments.username IS NOT "" and arguments.password IS NOT "">
  <cfquery
   name="thisquery"
   datasource="#arguments.dsn#"
   username="#arguments.username#"
   password="#arguments.password#">
   #arguments.sqlstring#
  </cfquery>
 <cfelse>
  <cfquery name="thisquery" datasource="#arguments.dsn#">
   #preservesinglequotes(arguments.sqlstring)#
  </cfquery> 
 </cfif>
 <cfreturn thisquery>
</cffunction>

<!--- begin the actual UDFs --->
<CFSCRIPT>
 function getProdDescrip(productid){
  var thisSQL = "select shortdescrip from products where baseid ='" & productid & "'";
  descrip = cfquery(dsn="rrtradingpost",sqlstring=thisSQL);
  return descrip.shortdescrip;
 }
</CFSCRIPT>
<!--- end UDFs --->

<cfset thisDescrip = getProdDescrip('BCPLB')>

<cfoutput>#thisDescrip#</cfoutput>

Step 1 is to turn any needed CF tags into functions using the CFFUNCTION tag, defining the tag’s attributes as arguments to the CFFUNCTION.

Then, within our CFSCRIPT section we can refer to the newly created function by name, passing in the pertinent arguments. That’s it!

One thing to consider with this is that you can't build a sql string that includes the cfqueryparam tag and have it work, so you'll have to compromise on this and just pass in your where clause values in native sql format (single quotes, etc.).

Keep it beautiful.  Doug out.

Posted by dougboude at 12:27 PM | PRINT THIS POST! | Link | 2 comments
12 July 2006
ByRef and ByVal in ColdFusion
Pop Quiz:
Using only your brain (stop laughing!), mentally execute the following code and figure out what values the returned structure will contain:


<cfset listvar = "1,2,3,4">
<cfset stCounter = structNew() >
<cfset stResults = structNew()>
<cfloop list="#listvar#" index="i">
    <cfset stCounter.item = i>
    <cfset stResults[i] = stCounter>
</cfloop>

Choices:
quiz choices
Early yesterday morning my choice would have been ‘B’. Late yesterday afternoon the answer was an obvious “D”. The reason is found in a little thing I like to call “ByRef” (Actually, the whole programming world calls it ByRef). ByRef stands for ‘By Reference’, and it is the polar opposite of ByVal (By Value).

Both of these terms refer to the way in which a value is passed along. You can equate it to the scenario of me giving you $100. If I hand you the cash, I have just given it to you “By Value”; you have it, it’s in your hand. If on the other hand I would have given you a check for $100, then I gave you the money “By Reference”; you don’t really have the money but you’re holding something that points to it.

ColdFusion by default passes structures by reference when you use them as a value. For instance, in the example of

<cfset thisVar = stResults>

(where stResults is a structure), my thisVar variable doesn’t actually have the stResults structure, but merely a pointer to the actual stResults. What this means is that if I modify stResults at some other place in my code and then go back to thisVar to get its value, thisVar’s value would also reflect the same changes. That’s really cool if you planned on things happening that way; not cool if you didn’t.

So how do we pass a structure by value and not by reference? Use the StructCopy function. Using the same example, if I set thisVar with

<cfset thisVar = StructCopy(stResults)>

then modifying the stResults structure afterwards will not reflect within thisVar’s value. StructCopy then is the equivalent of taking a snapshot of the structure, capturing it as it is at that point in time. Consider the following illustration:
byref byval example

If we want the pop quiz code at the beginning of this article to actually produce the answer ‘B’, then we need to make one small modification to it, like so:
<cfset listvar = "1,2,3,4">
<cfset stCounter = structNew() >
<cfset stResults = structNew()>
<cfloop list="#listvar#" index="i">
    <cfset stCounter.item = i>
    <cfset stResults[i] = StructCopy(stCounter)>
</cfloop>

So, boys and girls, that is the difference between ByRef and ByVal. Not typically something we have to ever specify in CF, but DEFINATELY something we have to be aware of since it affects our code (and causes us unnecessary amounts of troubleshooting time).
Posted by dougboude at 4:59 PM | PRINT THIS POST! | Link | 2 comments
08 July 2006
IF THE SOLUTION IS NOT BEAUTIFUL, THEN IT IS WRONG - STYLE, ELEGANCE, AND EFFICIENCY IN CODE
OUTPUTTING COMPLEX QUERY DATA

Preface to this Series

As with most coding challenges, there are always near infinite ways to overcome them. To say one approach is better than another is nearly always a matter of perspective and goals, but what I use as my personal measuring stick is the spirit of a quote by the late great Buckminster Fuller. It reads, “When I am working on a problem I never think about beauty. I only think about how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong.” I have adopted this quote as my personal and professional creed when it comes to problem solving. It also happens to be the motivation for this series, as I hope to inspire my peers to let their coding be a reflection of who they are as people. After all, we are artists and coding is the expression of our talents.

  

If the solution is not beautiful, it is wrong. With that in mind, let’s take a look at the first item of discussion that has led to a lot of “wrong” solutions out there. It’s one of the most basic of things, yet so often is done in such an ugly manner: outputting complex query data.

 

The Power of <CFOUTPUT>

For the most part, what I am about to share will not apply when talking about simply outputting a query, but rather comes heavily into play when we are given the complex task of outputting composite data from several sources that must be grouped and summarized. That, as many of you know, is where things can start to get really ugly and, without an understanding of one of our beloved CFOUTPUT’s lesser used attributes, there’s little hope of giving it any elegance at all.

  

The Scenario

 You are working on an application that manages projects for a large telecommunications company. Every project can be composed of several tasks, and each task can have multiple people assigned to it. Your challenge of the day is to produce a report showing all projects and their associated details. Here are the tables where your data is stored:

 

 

Here is the mockup you were given to duplicate for the report:

Report Sample

Now, time to build a solution. The more beautiful approach consists of two steps:

  1. Getting creative with our SQL statement,
  2. and nesting CFOUTPUT tags.

Creative SQL

The goal here is to retrieve EVERYTHING we need for our report in one query, the results of which will look like this:

working query

Here is the SQL statement for returning a query set like that previously shown:

 

 

SELECT Projects.projectName as A_ProjectName,  Tasks.taskName as B_TaskName, Roles.Rolename as C_RoleName, , Projects.CreatedDate AS projectCreationDate,
    Tasks.Description, Tasks.CreatedDa