-
Notifications
You must be signed in to change notification settings - Fork 24
Primer Part 4: Using Gateways to Manage Record Sets
- Preparation
- Getting organized
- CFFunction Overview
- Coldfusion Components
- Object Instantiation
- Calling the Model from the View
- Other Chapters
So far we've covered defining events and piecing together the user interface through the controller (mach-ii.xml). Now we need to move queries and business logic out of the View and into the Model. To get started, we'll move queries that return more than one record into Gateway objects using Coldfusion Components.
If you didn't switch to the version 2 files at the end of part 3:
- Rename the version 1 mach-ii.xml file to mach-ii.01.xml
- Rename mach-ii.02.xml to mach-ii.xml
Mach-II should be loading version 2 of mach-ii.xml using the "/02/" folders.
As we step through this process, we'll begin seeing version 3 files.
- A function is also known as a method.
- A Coldfusion Component (CFC) will often be referred to as an Object.
Open up version 2 of the contact list. It's pretty simple:
- HTML
<cfquery>
-
<cfoutput>
looping over more HTML to display the query records
<!--- /m2/views/contacts/02/contacts.cfm --->
<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
<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>
Let's first do something simple and organize the code so that all queries (and potentially any business logic) is run at the top of the page.
<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>
<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
<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>
contacts.cfm
is part of the presentation layer, so we need to move the
query over to the Model. Since this query returns a list of records it
should be moved into what's called a Gateway object.
If you've not used <cffunction>
before, lets go over its attributes.
<cffunction name="init" access="public" output="false" returntype="ContactGateway" hint="constructor">
-
name: The name of the function. No two functions in a CFC can have the same name.
-
access:
- A public function can be called from inside or outside the CFC.
- private functions can only be called by other functions in the same file.
- package access allows calls from functions in the same file and functions from other CFC files in the same folder (package).
- remote functions are used as Web Services, but that's a discussion for another primer
-
output: since we're only working with data and logic, always set this to false
-
returntype: the datatype returned from the function - string, numeric, struct, query, component name, etc.
Now take a look at getAllContacts()
. Notice we've set a variable
that's the same name as the <cfquery>
<cfset var qContacts = "" />
<cfquery name="qContacts" datasource="#variables.DSN#">
This creates the variable "qContacts" in the var scope, which makes it a
function local variable. Now "qContacts" is only available to the
function getAllContacts()
and cannot be read or have its value changed
by another function. This is important when you have more than one
function using variables with the same name.
Always var scope names of queries, cfloop indexes and other tag-defined variables
varScoper is a tool designed by Mike Schierberl "to identify variables created within a cffunction that don't have a corresponding (cfset var) statement."
Here is ContactGateway.cfc
which contains two functions.
<!--- /m2/model/contacts/03/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>
This is the constructor method. This method will initialize the component, set data into the variables scope of the component and return an instance of the component.
The returntype attribute should specify the CFC file name with or without the dot separated path to the component.
<cffunction name="init"
access="public"
output="false"
returntype="ContactGateway"
hint="constructor">
or
<cffunction name="init"
access="public"
output="false"
returntype="model.contacts.ContactGateway"
hint="constructor">
In order to pass existing variables into a component, you can inject those values into the object using the variables scope.
<cfargument name="DSN" type="string" required="true" hint="datasource" />
<cfset variables.DSN = arguments.DSN />
Finally, return the object.
<cfreturn this />
This runs the query "qContacts" and returns the recordset. There are three things to note about this function:
- uses the returntype "query"
- the name of the query is var scoped
- the cfquery uses the datasource "#variables.DSN#", which was defined by the init() method
To create an instance of the Gateway object:
<cfset contactGateway = createObject(
"component",
"model.contacts.ContactGateway").init( session.DSN )
/>
You want to avoid calling shared scoped variables (i.e. application.*, session.*) from inside a CFC, so make sure to inject them into the component through the init() method.
If the value of session.DSN is changed outside of the CFC, the value "inside" the CFC (in the variables scope) is unaffected.
However, if the session variable is a non-simple datatype like a struct or another component (i.e. a Bean), changing the value inside the CFC will change the value of the actual session variable outside the CFC. See There are no Pointers in ColdFusion for more information.
Let's go back to contacts.cfm and get the contact records from the Gateway object
<cfset qContacts = createObject("component", "model.contacts.ContactGatway").init( request.DSN ) />
<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
<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>
You can see all the Gateway objects at this point under the version 3 folders.
We've started moving queries out of the View and into the Model, but now we're creating objects in the View. Anytime the object definition changes, we may need to update all the View files that reference it and that's what we were trying to avoid with the queries.
Using Mach-II Listeners we'll remove object instantiation from the View and reference objects and methods from the Controller (mach-ii.xml).
- Mach-II Primer Series Navigation
- Part 1 - Files versus Events
- Part 2 - Variables versus Arguments
- Part 3 - Flip This Code
- Part 4 - Using Gateways to manage record sets
- Part 5 - Mediating events with Listeners
- Part 6 - Getting the details with Beans and DAOs
- Part 7 - Processing data with Beans and DAOs using Event-Filters
Special thanks to Adrian J. Moreno of IKnowKungFoo for contributing this series of primers.