One of the biggest reasons to move towards using OO concepts with Coldfusion is to build modular, extendable code libraries which are very easy to reuse throughout an application or group of applications. As a start, we'll begin by moving queries that generally return multiple records into Gateway objects.

Changing your code perspective

If you've gone through my Mach-II Primer, you've probably started rethinking how you arrange your code. If not, let's start with some old school, procedurally programmed Coldfusion code:

Database table
CF_Contacts
CONTACT_ID CONTACT_FIRST_NAME CONTACT_LAST_NAME
1 John Smith
2 Jane Doe
3 Juan Gonzalez

/contacts/contact_list.cfm
view plain print about
1<cfquery name="qContacts" datasource="#request.DSN#">
2    SELECT
3        CONTACT_ID,
4        CONTACT_FIRST_NAME,
5        CONTACT_LAST_NAME
6    FROM
7        CF_CONTACTS
8    ORDER BY
9        CONTACT_LAST_NAME, CONTACT_FIRST_NAME
10</cfquery>
11
12<ul>
13    <cfoutput query="qContacts">
14        <li>
15            [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
16            <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
17        </li>
18    </cfoutput>
19</ul>

This outputs something like:

The query qContacts will usually return a record set containing more than one record. These types of queries are generally placed into a Gateway Object.

/cfc/mysite/contacts/ContactGateway.cfc
view plain print about
1<cfcomponent name="ContactGateway" output="false" hint="Defines Gateway functions for Contacts">
2
3    <cffunction name="init" access="public" output="false" returntype="ContactGateway" hint="constructor">
4        <cfargument name="DSN" type="string" required="true" hint="datasource" />
5        <cfset variables.DSN = arguments.DSN />
6        <cfreturn this />
7    </cffunction>
8
9    <cffunction name="getAllContacts" access="public" output="false" returntype="query" hint="returns a query recordset of contacts">
10
11        <cfset var qContacts = "" />
12
13        <cfquery name="qContacts" datasource="#variables.DSN#">
14            SELECT
15                CONTACT_ID,
16                CONTACT_FIRST_NAME,
17                CONTACT_LAST_NAME
18            FROM
19                CF_CONTACTS
20            ORDER BY
21                CONTACT_LAST_NAME,
22                CONTACT_FIRST_NAME
23        </cfquery>
24        
25        <cfreturn qContacts />
26
27    </cffunction>
28
29</cfcomponent>

As outlined in the first four parts of this primer, the object ContactGateway.cfc has two functions:

init()

The constuctor method init() takes a single argument which is the value of the datasource. Since the same datasource will be used for every query throughout this application, it will be placed in the variables scope of the object.

view plain print about
1<cfset variables.DSN = arguments.DSN />

getAllContacts()

This function wraps the same query that has been moved over from the CFM page. Notice that the name of the query has been var scoped, making qContacts a function local variable.

The only change to the query has been to value of the datasource. Instead of reading from the request scope of the CFM file, it's been placed into the variables scope of the object via init().

view plain print about
1<cfquery name="qContacts" datasource="#variables.DSN#">

So how do I get the data now?

Instead of writing the query on the same CFM file that will output its results, we'll create an instance of the Gateway Object and access the query through it.

/contacts/contact_list.cfm
view plain print about
1<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
2<cfset qContacts = contactGateway.getAllContacts() />
3
4<ul>
5    <cfoutput query="qContacts">
6        <li>
7            [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
8            <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
9        </li>
10    </cfoutput>
11</ul>

What about user specific data?

First, let's expand our database a bit. We'll add a Users table and a UserContacts table.

Database

Users
USER_ID USER_NAME
1 hpotter
2 rweasley
3 hgrainger

(Guess which books I've started reading?)

CF_Contacts
CONTACT_ID CONTACT_FIRST_NAME CONTACT_LAST_NAME
1 John Smith
2 Jane Doe
3 Juan Gonzalez

This table associates Users with Contacts

UserContacts
USER_ID CONTACT_ID
1 1
1 2
2 3
3 2

Next we'll have to update our query to bring back only Contacts associated to a specific User or all Contacts if no user is specified.

Updated getAllContacts()
view plain print about
1<cffunction name="getAllContacts" access="public" output="false" returntype="query" hint="returns a query recordset of contacts">
2
3    <cfargument name="USER_ID" type="numeric" required="true" default="0" hint="Passing no value will return all Contacts" />
4
5    <cfset var qContacts = "" />
6
7    <cfquery name="qContacts" datasource="#variables.DSN#">
8        SELECT
9            a.CONTACT_ID,
10            a.CONTACT_FIRST_NAME,
11            a.CONTACT_LAST_NAME
12        FROM
13            CF_CONTACTS a
14        <cfif arguments.USER_ID neq 0>
15            LEFT JOIN
16                USERCONTACTS b
17                ON
18                b.CONTACT_ID = a.CONTACT_ID
19                AND
20                b.USER_ID = <cfqueryparam value="#arguments.USER_ID#" cfsqltype="cf_sql_integer" />
21        </cfif>
22        ORDER BY
23            a.CONTACT_LAST_NAME,
24            a.CONTACT_FIRST_NAME
25    </cfquery>
26    
27    <cfreturn qContacts />
28
29</cffunction>

Finally, we need to change how we're requesting this data. Let's assume that the currently logged in user's UserID is stored in the variable session.userID.

Retrieving user specific data
view plain print about
1<cfparam name="session.userID" type="numeric" default="0" />
2
3<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
4<cfset qContacts = contactGateway.getAllContacts( USER_ID = session.userID ) />

So if Mr. Potter is logged into the site, the page should only output

You could easily reuse the same code in an Administrative section of the application, where one user can control others, as well as alter records associated to them. If you had a list of Employees, you could pass the EmployeeID through a URL variable to the same function in order to get their contacts.

Administrative task

Reducing calls to createObject()

As the code stands now, the ContactGateway.cfc object is created every time the page is requested and placed into the variables scope of the CFM file. Also, this object is used by every user of the application, but there is no user specific data stored in the variables scope of the CFC file.

Rather than creating the object over and over, it can be created once and placed into the application scope. This places the object in memory for the life of the application, reducing processing time and the overall memory usage of the application.

The simplest place to do this is in the Application.cfc file inside the onApplictionStart method. This will ensure that the object is created and placed in the application scope when the application is loaded for the first time.

Application.cfc : onApplicationStart()
view plain print about
1<cfset application.DSN = "myData" />
2
3<cfset application.contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = application.DSN ) />

Now we can remove yet another line of code from our CFM file:

/contacts/contact_list.cfm
view plain print about
1<cfset qContacts = application.contactGateway.getAllContacts() />
2<!--- or --->
3<cfparam name="session.userID" type="numeric" default="0" />
4
5<cfset qContacts = application.contactGateway.getAllContacts( USER_ID = session.userID ) />

I can't say that you should place a Gateway object into the application scope 100% of the time, but I can say that you should do it more often than not.

What does this accomplish?

Instead of a single file containing a query and HTML, we now have two files with even a little more code than when we started.

By splitting the code into two files, we gain two things:

  1. There is a reduction of the amount of code in a file that comprises part of your application's user interface (UI), a file that is commonly referred to as a View.
  2. The query is now available to the application as a reusable chunk of code via a Gateway object, making it part of the application's Model.

Placing the object into application memory drastically reduces page load times and helps to decrease the overall footprint of the application.

Even if you don't use a published framework like Mach-II, Model-Glue or Fusebox to develop your application, separating code that belongs in the Model from code that manages the View can not only simplify your initial development process, it can reduce future development as well.

Next up

We can handle multiple records in the Model, now we need to deal with them one at a time. Next we chase down the elusive Bean Object.