i Know Kung Foo Consulting

Object Oriented Coldfusion : 7.2 : Basic DAO Example

Ok, so we've got the concept of a DAO and how it interacts with a bean. Here's an example of a form and an action page using Object Oriented Coldfusion.

A Form and an Action page

Let's say you've got a page with a list of Contacts, each item has a link to contact_form.cfm with the CONTACT_ID in the query string.

contact_list.cfm
<cfset contactList = application.ContactGateway.getContacts() />

<ul>
<cfoutput query="contactList">
   <li>
      <a href="contact_form.cfm?CONTACT_ID=#contactList.CONTACT_ID#">
         #contactList.LAST_NAME#, #contactList.FIRST_NAME#
      </a>
   </li>
</cfoutput>
</ul>

output

Updating a record

This form is going to update an existing record.

contact_form.cfm?CONTACT_ID=3
<cfparam name="url.CONTACT_ID" type="numeric" default="0" />

<!--- List of Categories --->
<cfset categories = application.categoryGateway.getCategories() />
<!--- Instance of a Contact Bean --->
<cfset contact = createObject("component", "Contact").init( CONTACT_ID = url.CONTACT_ID ) />
<!--- Populate Contact Bean --->
<cfset application.contactDAO.read( contact ) />

<cfoutput>
<form name="contact_data" action="contact_update.cfm" method="post" onsubmit="return false;">

   <input type="hidden" name="CONTACT_ID" value="#contact.getContactID()#" />

   <p>Category: <select name="CATEGORY_ID">
      <option value="0">-- Select --</option>
      <cfloop query="categories">
         <cfif contact.getCategoryID() eq categories.CATEGORY_ID>
            <option value="#categories.CATEGORY_ID#" selected="selected">#categories.CATEGORY_LABEL#</option>
         <cfelse>
            <option value="#categories.CATEGORY_ID#">#categories.CATEGORY_LABEL#</option>
         </cfif>
      </cfloop>
   </select></p>

   <p>First Name: <input type="text" name="FIRST_NAME" value="#contact.getFirstName()#" /></p>

   <p>Last Name: <input type="text" name="LAST_NAME" value="#contact.getLastName()#" /></p>

   <p><input type="submit" name="processContact" /></p>

</form>
</cfoutput>

output

Category:

First Name:

Last Name:

As for the processing page to update this Contact record, you have a few options:

contact_update.cfm 1. Passing ordered argument values:
<cfif structKeyExists(form, "processContact")>
   
   <cfset contact = createObject("component", "Contact").init(
         form.CONTACT_ID,
         form.CATEGORY_ID,
         form.FIRST_NAME,
         form.LAST_NAME
         ) />


   <cfset application.contactDAO.update( contact ) />
         
   <cflocation url="contact_list.cfm" addtoken="false" />
   
</cfif>

2a. Passing name / value pairs:

<cfif structKeyExists(form, "processContact")>
   
   <cfset contact = createObject("component", "Contact").init(
         CONTACT_ID = form.CONTACT_ID,
         CATEGORY_ID = form.CATEGORY_ID,
         FIRST_NAME = form.FIRST_NAME,
         LAST_NAME = form.LAST_NAME
         ) />


   <cfset application.contactDAO.update( contact ) />
         
   <cflocation url="contact_list.cfm" addtoken="false" />
   
</cfif>

2b. Passing name / value pairs using cfinvoke:

<cfif structKeyExists(form, "processContact")>

   <cfinvoke
      component="Contact"
      method="init">

      
      <cfinvokeargument name="CONTACT_ID"
         value="#form.CONTACT_ID#" />

      <cfinvokeargument name="CATEGORY_ID"
         value="#form.CATEGORY_ID#" />

      <cfinvokeargument name="FIRST_NAME"
         value="#form.FIRST_NAME#" />

      <cfinvokeargument name="LAST_NAME"
         value="#form.LAST_NAME#" />

         
   </cfinvoke>
   
   <cfinvoke
      component="#application.ContactDAO#"
      method="update">

      
      <cfinvokeargument name="Contact"
         value="#contact#" />

         
   </cfinvoke>
      
   <cflocation url="contact_list.cfm" addtoken="false" />
   
</cfif>

3a. Since the form field names match the argument names of the Update() method, you can pass the form scope as an Argument Collection:

<cfif structKeyExists(form, "processContact")>

   <cfset contact = createObject("component", "Contact").init(
         argumentCollection = form
         ) />


   <cfset application.contactDAO.update( contact ) />
         
   <cflocation url="contact_list.cfm" addtoken="false" />
   
</cfif>

3b. Passing an Argument Collection using cfinvoke:

<cfif structKeyExists(form, "processContact")>
   
   <cfinvoke
      component="Contact"
      method="init"
      argumentCollection="form" />


   <cfinvoke
      component="#application.ContactDAO#"
      method="update" />

      
       <cfinvokeargument name="Contact"
         value="#contact#" />

         
   </cfinvoke>
      
   <cflocation url="contact_list.cfm" addtoken="false" />
   
</cfif>

Creating a record

If you wanted to create a new Contact, you could simply pass CONTACT_ID=0 in the query string to contact_form.cfm. This would create an empty bean and therefore an empty form.

Now the question that should come to mind here is, "why should I read from the database when CONTACT_ID=0? The answer is, you shouldn't. With a simple update to the read() method, we can bypass running the query and re-populating the bean under this condition.

ContactDAO.cfc - read()
<cffunction name="read" access="public" output="false" returntype="boolean">

   <cfargument name="contact" required="true" type="Contact" hint="Contact bean" />

   <cfset var qReadOne = "" />

   <!--- Bypass the query and init() when CONTACT_ID = 0 --->
   <cfif arguments.contact.getContactID() neq 0>

      <cfquery name="qReadOne" datasource="#variables.DSN#">
         SELECT
            CONTACT_ID,
            CATEGORY_ID,
            FIRST_NAME,
            LAST_NAME
         FROM
            CONTACTS
         WHERE
            CONTACT_ID = <cfqueryparam value="#arguments.contact.getContactID()#" cfsqltype="cf_sql_integer" />
      </cfquery>

      <cfif qReadOne.recordcount eq 0>

         <cfreturn false />

      <cfelse>

         <cfset arguments.contact.init(
               CONTACT_ID = qReadOne.CONTACT_ID,
               CATEGORY_ID = qReadOne.CATEGORY_ID,
               FIRST_NAME = qReadOne.FIRST_NAME,
               LAST_NAME = qReadOne.LAST_NAME
               ) />


      </cfif>

   </cfif>

   <cfreturn true />

</cffunction>

The only change you'd make to contact_form.cfm would be to set the action page to something like contact_create.cfm in order to run the create() method instead of the update() method from ContactDAO.cfc.

contact_form.cfm - dynamic action
<cfparam name="url.CONTACT_ID" type="numeric" default="0" />
<cfparam name="request.actionPage" type="string" default="contact_create.cfm" />

<cfif url.CONTACT_ID neq 0>
   <cfset request.actionPage = "contact_update.cfm" />
</cfif>

<!--- List of Categories --->
<cfset categories = application.categoryGateway.getCategories() />
<!--- Instance of a Contact Bean --->
<cfset contact = createObject("component", "Contact").init( CONTACT_ID = url.CONTACT_ID ) />
<!--- Populate Contact Bean --->
<cfset application.contactDAO.read( contact ) />

<form name="contact_data" action="#request.actionPage#" method="post">

Now we're using the same contact_form.cfm to both Update and Create a Contact. The only difference is the output:

output

Category:

First Name:

Last Name:

Finally, here's the action page for creating a Contact:

contact_create.cfm
<cfif structKeyExists(form, "processContact")>
   
   <cfset contact = createObject("component", "Contact").init(
         CONTACT_ID = form.CONTACT_ID,
         CATEGORY_ID = form.CATEGORY_ID,
         FIRST_NAME = form.FIRST_NAME,
         LAST_NAME = form.LAST_NAME
         ) />


   <cfset application.contactDAO.create( contact ) />

<cflocation url="contact_list.cfm" addtoken="false" />

</cfif>

Does that look familiar? The only difference between contact_create.cfm and contact_update.cfm is that we're calling create() instead of update(). Even though the create() method doesn't require the CONTACT_ID, since we're passing data in name / value pairs, the order of the arguments doesn't matter. CONTACT_ID will be passed to the method, but it just won't be used for any part of the process that creates a record in the database.

Where's Hagrid?

When we first meet Rubeus Hagrid in "Harry Potter and The Sorcerer's Stone" (actually, in "The Philosopher's Stone") he is the Gamekeeper for Hogwarts School of Witchcraft and Wizardry. In the second book, "The Chamber of Secrets", Hagrid has kept his Staff position, but is now also an Instructor, teaching Care of Magical Creatures.

As the database layout stands now, each Contact can have only one Category. In order to properly enter Professor Hagrid into the Hogwarts database, we'll have to make some changes to the database tables to allow a single Contact record to map to multiple Category records and vice versa.

We'll also need a more Complex Data Access Object to handle this new many-to-many (*:*) relationship.


Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
David Stockton's Gravatar Hi,

I've just picked this up from MXNA and scanned your article but don't you think your update method:

<cfset application.contactDAO.update(
form.CONTACT_ID,
form.CATEGORY_ID,
form.FIRST_NAME,
form.LAST_NAME
) />

Should instead take a contact business object(/bean/whatever your terminoligy) instead of name:value pairs? This would also fit with the update method from your previous article?

Also, I favor calling contactDAO.create( contactBO ) rather than passing in name:value pairs. The DAO should not be used in this way because the BO *could* do some important work behind the scenes.

Just my 2p.

Rgds,
D

PS: Appologies if in my quick scan I've misread and you are infact doing this.
# Posted By David Stockton | 10/23/07 12:23 PM
Adrian J. Moreno's Gravatar That's what I get for writing for late at night. The first line for each of those should be passing the arguments to the init() of the bean, then passing the bean to the method of the DAO.

I'll correct this asap. Thanks for pointing it out.
# Posted By Adrian J. Moreno | 10/23/07 12:31 PM
Emre Akkas's Gravatar Your OOP series helped me understand the concept much better. Thanks very much for posting it
# Posted By Emre Akkas | 1/17/08 2:25 AM
Mike's Gravatar Very nice thank you very much !

Something about form validation would be very helpfull now !
# Posted By Mike | 1/22/08 1:17 PM
Adrian J. Moreno's Gravatar @Mike: I'll put form validation on my to-do list. I'm glad you like the series.
# Posted By Adrian J. Moreno | 1/22/08 1:38 PM
Tim's Gravatar Sorry I'm a little late to the game... I was wondering if there was a post where you divulge what happened to Hagrid and his many-to-many situation with the "contact" and "category" entities?

Also, I'd like to thank you for the clear and concise nature of the series to date. Great work!
# Posted By Tim | 2/20/08 11:48 AM
Adrian J. Moreno's Gravatar @Tim: It's almost ready. I'm trying to have some more posts done by the weekend.
# Posted By Adrian J. Moreno | 2/20/08 1:15 PM
Matt's Gravatar I've seen this before on other OOP articles.
Why do you do this before a cfquery

<cfset var qReadOne = "" />

Does it make things faster by reserving some memory for the query or something?

Excellent article BTW

Matt
# Posted By Matt | 2/22/08 12:41 PM
Adrian J. Moreno's Gravatar @Matt: If you haven't read part 4, check it out.

http://www.iknowkungfoo.com/blog/index.cfm/2007/8/...

The var scope makes a variable local to a function. If the query variable is not var scoped, the some outside process could alter its value before it is returned by the function.

For example, under load, as more requests are made to that function the value of qReadOne will be changing very quickly.

If it is not var scoped, then multiple threads would be calling that query and changing the value of that variable. It's then very possible that ThreadA would get the value of the query that was generated by ThreadB or ThreadC.

HTH

P.S. I'm glad you liked the article.
# Posted By Adrian J. Moreno | 2/22/08 2:08 PM
Matt's Gravatar So a Coldfusion query object will be scoped to a local variable if you define the variable before hand? so what if i did
<cfset request.myquery = 0>
Then did
<cfquery name="myquery">SELECT BLAH FROM BLAH</cfquery>

My query would be available in the request scope? (not that i would do this obviously)

This is news to me, it really opens a can of worms! I had no idea it would effect threads in this way.
# Posted By Matt | 2/25/08 10:30 AM
Adrian J. Moreno's Gravatar Off the top of my head, i think that you would have two variables in that case.

request.myquery = 0 that is available inside and outside of the CFC.

variables.myquery = {query} that is available to any method inside the CFC.

But, if inside the CFC you were to do request.myquery = variables.myquery, then the value of request.myquery outside of the CFC would be the {query} recordset and not 0.
# Posted By Adrian J. Moreno | 2/25/08 11:49 PM
Matt's Gravatar What I mean is that how does the cfquery know that the query variable "myquery" should only be local to the function, is this just how it works by nature or does setting the var myquery = 0 tell coldfusion that all variables with the name "myquery" from now on will only be set local to the function.

This is why I was wondering about request and if the same rules applied. Sorry if i'm being thick and not understanding your answers.
# Posted By Matt | 2/26/08 1:45 PM
Adrian J. Moreno's Gravatar @Matt, you're not thick, I think I misunderstood what you were asking.

Yes, this is "just how it works" in the sense that CF has a hierarchy of variable scopes.

When you ask for a variable, it searches for its definition through the different variable scopes in order.

* Arguments
* Variables (Local)
* CGI
* File
* URL
* Form
* Cookie
* Client

Ref: http://www.depressedpress.com/Content/Development/...
# Posted By Adrian J. Moreno | 2/26/08 4:07 PM
Matt's Gravatar I see,
So we aren't setting 2 variables?
<cfset var myquery = 0> <--setting 1 here
<cfquery name="myquery"></cfquery> <-- setting another 1 here

instead <cfquery name="myquery"> knows that <cfset var myquery = 0> has been set before it therefore knows it has to be local.
# Posted By Matt | 2/27/08 7:36 AM
Adrian J. Moreno's Gravatar Right. Since CF can switch variable types on the fly, as opposed to Java where variables have to be typed when declared, as long as we have declared a variable to the var scope, any reference to that variable within the function is kept local to that function, regardless if the variable has changed data type.

I think I'll update part four with some of this discussion.
# Posted By Adrian J. Moreno | 2/27/08 2:29 PM
Matt's Gravatar Thats great, thanks for your help Adrian, I think I understand it much better now
# Posted By Matt | 2/27/08 2:48 PM
ernie's Gravatar This series has really helped me, I can't wait for the next issue!
# Posted By ernie | 3/10/08 2:53 AM
Patrick Whittingham's Gravatar Instead of having multiple cfc's can one have just 1 cfc?
# Posted By Patrick Whittingham | 3/20/08 8:25 AM
Adrian J. Moreno's Gravatar @Patrick, I posted an answer to your question here:

http://www.iknowkungfoo.com/blog/index.cfm/2008/3/...
# Posted By Adrian J. Moreno | 3/20/08 1:58 PM
Dale Hans's Gravatar I was following along creating the files so I could play with the code more closely and I hit what appeared to be two ommisions in your code. I made the following changes to the contact_form.cfm file to get the code to work.

1. I removed the onSubmit="return false;" code so the form would submit
<form name="contact_data" action="contact_update.cfm" method="post" >

2. I added the name "processContact" to the submit button so the contact_update.cfm page would catch the form's existance.
<input type="submit" name="processContact" />

If I am missing something here, please let me know.
Dale
# Posted By Dale Hans | 5/9/08 5:10 AM
Adrian J. Moreno's Gravatar @Dale: #1 I put that in to show the rendered HTML but stop the form from being submitted on the site.

#2: Thanks for catching that. I've updated the code in that section.
# Posted By Adrian J. Moreno | 5/10/08 3:02 PM
Quan Tran's Gravatar Going back to multiple cfc's vs a single cfc.

Would it be more "OO" if we inject the DAO and Gateway objects into the Bean that way we only deal with 1 CFC?
I like the seperation of code, but i don't like having to deal with 3 cfc's.

Example would be we have a cfc that will return our objects with the injected dao and gatways:

SOMECFC.getNewContract() would create the contract bean, contract.setDAO(ContractDAO), contract.setGateway(contractgatway) and return it. Contract would have to have the methods of both the DAO and gateway to expose them through the bean. (May not be needed? if we can dynamicly create these methods automatically through the setter of dao and gateway? not sure if possible.)

So we have:
Contract.save() which is just getDAO.Save(this)?

Would this make sense? or am i just better off just creating a single object if i only want to deal with 1 cfc.
# Posted By Quan Tran | 5/14/08 11:49 AM
Adrian J. Moreno's Gravatar @Quan: you would not want to put the DAO or Gateway into the Bean. The Bean should only contain data, not interact with the database. There is a pattern that defines a hybrid DAO/Bean, but my personal preference is to avoid that situation.

Yes having multiple CFCs can get to be a pain, but it can be beneficial in the long run. I've got a post planned on merging the DAO and Gateway into a single CFC or using the Gateway within the DAO. I'm also working on a post about the Service Object, which would handle how related CFCs interact with each other.

I'll try to have the next post in this series online this weekend. I've had some things come up lately and I'll post about some of that later today.

Also, comments are moderated to cut down on spam, so you won't see your comment until I approve it.
# Posted By Adrian J. Moreno | 5/14/08 1:24 PM

Copyright © 2001 - 2008 Adrian J. Moreno and i Know Kung Foo Consulting
BlogCFC was created by Raymond Camden. This blog is running version 5.9.001.