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.
Updating a record
This form is going to update an existing record.
<!--- 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>
As for the processing page to update this Contact record, you have a few options:
<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:
<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:
<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:
<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:
<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.
<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.
<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:
Finally, here's the action page for creating a Contact:
<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.








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.
I'll correct this asap. Thanks for pointing it out.
Something about form validation would be very helpfull now !
Also, I'd like to thank you for the clear and concise nature of the series to date. Great work!
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
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.
<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.
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.
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.
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/...
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.
I think I'll update part four with some of this discussion.
http://www.iknowkungfoo.com/blog/index.cfm/2008/3/...
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
#2: Thanks for catching that. I've updated the code in that section.
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.
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.