Skip to content

Primer Part 4: Using Gateways to Manage Record Sets

thofrey edited this page Apr 1, 2014 · 7 revisions

Table of Contents

  1. Preparation
  2. Getting organized
  3. CFFunction Overview
  4. Coldfusion Components
  5. Object Instantiation
  6. Calling the Model from the View
  7. 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.

Preparation

We'll begin with the Version 2 files

If you didn't switch to the version 2 files at the end of part 3:

  1. Rename the version 1 mach-ii.xml file to mach-ii.01.xml
  2. 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.

Terms to know:

  • A function is also known as a method.
  • A Coldfusion Component (CFC) will often be referred to as an Object.

Getting organized

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.

CFFunction Overview

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

Coldfusion Components

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>

init()

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

getAllContacts()

This runs the query "qContacts" and returns the recordset. There are three things to note about this function:

  1. uses the returntype "query"
  2. the name of the query is var scoped
  3. the cfquery uses the datasource "#variables.DSN#", which was defined by the init() method

Object Instantiation

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.

Calling the Model from the View

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.

Other Chapters

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

Special thanks to Adrian J. Moreno of IKnowKungFoo for contributing this series of primers.

Clone this wiki locally