Object Oriented Coldfusion : 5 : The Gateway Object

Posted on September 11, 2007

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

<cfquery name="qContacts" datasource="#request.DSN#">
  SELECT
    CONTACT_ID,
    CONTACT_FIRST_NAME,
    CONTACT_LAST_NAME
  FROM
    CF_CONTACTS
  ORDER BY
    CONTACT_LAST_NAME, CONTACT_FIRST_NAME
</cfquery>

<ul>
  <cfoutput query="qContacts">
    <li>
      [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
      <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">
        #qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#
      </a>
    </li>
  </cfoutput>
  </ul>

This outputs something like:

  • [Edit] John Smith
  • [Edit] Jane Doe
  • [Edit] Juan Gonzalez

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

<cfcomponent name="ContactGateway" output="false" hint="Defines Gateway functions for Contacts">

  <cffunction name="init" access="public" output="false" returntype="ContactGateway" hint="constructor">
    <cfargument name="DSN" type="string" required="true" hint="datasource" />
    <cfset variables.DSN = arguments.DSN />
    <cfreturn this />
  </cffunction>

  <cffunction name="getAllContacts" access="public" output="false" returntype="query" 
    hint="returns a query recordset of contacts">
    <cfset var qContacts = "" />

    <cfquery name="qContacts" datasource="#variables.DSN#">
      SELECT
        CONTACT_ID,
        CONTACT_FIRST_NAME,
        CONTACT_LAST_NAME
      FROM
        CF_CONTACTS
      ORDER BY
        CONTACT_LAST_NAME,
        CONTACT_FIRST_NAME
    </cfquery>

    <cfreturn qContacts />
  </cffunction>

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

<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().

<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

<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts() />

<ul>
  <cfoutput query="qContacts">
    <li>
      [ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
      <a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">
        #qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#
      </a>
    </li>
  </cfoutput>
</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

USER_ID USER_NAME
1 hpotter
2 rweasley
3 hgrainger

[Table: Users]

(Guess which books I’ve started reading?)

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

[Table: CF_Contacts]

This table associates Users with Contacts

USER_ID CONTACT_ID
1 1
1 2
2 3
3 2

[Table: UserContacts]

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()

<cffunction name="getAllContacts" access="public" output="false" returntype="query" 
  hint="returns a query recordset of contacts">

  <cfargument name="USER_ID" type="numeric" required="true" default="0" 
    hint="Passing no value will return all Contacts" />

  <cfset var qContacts = "" />

  <cfquery name="qContacts" datasource="#variables.DSN#">
    SELECT
      a.CONTACT_ID,
      a.CONTACT_FIRST_NAME,
      a.CONTACT_LAST_NAME
    FROM
      CF_CONTACTS a
    <cfif arguments.USER_ID neq 0>
      LEFT JOIN
        USERCONTACTS b
        ON
        b.CONTACT_ID = a.CONTACT_ID
        AND
        b.USER_ID = <cfqueryparam value="#arguments.USER_ID#" cfsqltype="cf_sql_integer" />
    </cfif>
    ORDER BY
      a.CONTACT_LAST_NAME,
      a.CONTACT_FIRST_NAME
  </cfquery>

  <cfreturn qContacts />

</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

<cfparam name="session.userID" type="numeric" default="0" />

<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts( USER_ID = session.userID ) />

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

  • [ Edit ] John Smith
  • [ Edit ] Jane Doe

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

<cfparam name="url.EmployeeID" type="numeric" default="0" />

<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts( USER_ID = url.EmployeeID ) />

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()

<cfset application.DSN = "myData" />

<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

<cfset qContacts = application.contactGateway.getAllContacts() />
<!--- or --->
<cfparam name="session.userID" type="numeric" default="0" />

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

About the Author
Adrian J. Moreno

Adrian is a CTO and solution architect specializing in software modernization. More information