Creating the Gateway Object - ColdBox Series Part 7b

In my last post we began creating out Gateway Object. It was a lot more work then I thought it was going to be, hence, this is the second part of creating a gateway object. Hopefully, we won't get to three. But the fact of the matter is, we need these things in place so that we can create our ColdBox application. So suck-it-up :)

Previously...

In the last post we created 10 methods, for some we used the Illudium PU-36 code generator, and for some we used our insanely fast typing abilities. The methods we've created so far are:

  • createList(list:List):boolean
  • readList(list:List):void
  • updateList(list:List):boolean
  • deleteList(list:List)boolean
  • createItem(item:Item):boolean
  • readItem(item:Item):void
  • updateItem(item:Item):boolean
  • deleteItem(item:Item):boolean
  • getAllItemsAsQuery(id:Numeric):Query
  • queryRowToStruct(qry:Query):Struct

After we created our methods, we modified our readList() method to return a List object with its child Item objects in a private property of type Array.

Modifying updateList() to include child objects

Now we need to make the same modifications to the updateList() and deleteList() methods.

To make things simple we are going to assume that if a list gets deleted that all of its items will be deleted. We are also not going to try to be clever and move things from list-to-list or have the same items on more than one list.

So our current updateList() method looks like this:


    <cffunction name="updateList" access="public" output="false" returntype="boolean">
        <cfargument name="list" type="model.todos.list" required="true" />
        <cfset var qUpdate = "" />

        <cftry>
            <cfquery name="qUpdate" datasource="#variables.dsn#">
                UPDATE    lists
                SET    name = <cfqueryparam value="#arguments.list.getname()#" CFSQLType="cf_sql_varchar" />,
                    priority = <cfqueryparam value="#arguments.list.getpriority()#" CFSQLType="cf_sql_integer" null="#not len(arguments.list.getpriority())#" />
                WHERE    listid = <cfqueryparam value="#arguments.list.getlistid()#" CFSQLType="cf_sql_integer" />
            </cfquery>

            <cfcatch type="database">
                <cfreturn false />
            </cfcatch>

        </cftry>

        <cfreturn true />
    </cffunction>

Just like our readList() method, this one takes in a List bean that was created elsewhere. Also, the bean has probably been modified since it was created or since it was last retrieved from the DB using the readList() method. Just remember that the List bean was created elsewhere, modified and is now being passed into the udpateList() method to be persisted to the database.

If the update is completed successfully, the method returns true. If anything fails, it will return false.

Now we need to set up our updateList() method to also call the updateItem() method for each of the item objects stored in the List's private property Items.

What do we need to do?

  1. Get the items array out of the List object
  2. Loop over the array, calling updateItem() for each
  3. Handle any errors that might occur

So here is the code we are going to add to our updateList() method:


    <!--- Add this line at the top with the other local variables --->
    <cfset var aItems = arguments.list.getItems() />

    <!--- Add this block within the <cftry></cftry> and before <cfcatch></cfcatch> --->
    <cfloop from="1" to="#ArrayLen(aItems)#" index="iItems">
        <cfset updateItem(iItems) />
    </cfloop>

Adding transaction Control

Now we have an issue we need to address. Let's think for a moment about handling problems that may arise. In our updateList() method we have at least one UPDATE query going to the database, but depending on how many items we have, we could have 1, 5, 10, or even dozens more. What happens if there is a problem with the database or the connection to the database right in the middle of all those UPDATE queries? Well, the method will return false because the try/catch will fail, but the some of the queries will have been completed and some will not have, and our List object and its children will be out of sync with the database.

So how can we handle this?

We will add transaction control to these UPDATE queries so that if anything fails, that it all rolls back to its previous state.

So here is our completed updateLists() method. Note: I have NOT tested this code yet.


    <cffunction name="updateList" access="public" output="false" returntype="boolean">
        <cfargument name="list" type="model.todos.list" required="true" />
        <cfset var qUpdate = "" />
        <cfset var aItems = arguments.list.getItems() />
    
        <cftransaction action="begin">
        <cftry>
            <cfquery name="qUpdate" datasource="#variables.dsn#">
                UPDATE    lists
                SET    name = <cfqueryparam value="#arguments.list.getname()#" CFSQLType="cf_sql_varchar" />,
                    priority = <cfqueryparam value="#arguments.list.getpriority()#" CFSQLType="cf_sql_integer" null="#not len(arguments.list.getpriority())#" />
                WHERE    listid = <cfqueryparam value="#arguments.list.getlistid()#" CFSQLType="cf_sql_integer" />
            </cfquery>

            <cfloop from="1" to="#ArrayLen(aItems)#" index="iItems">
                <cfset updateItem(iItems) />
            </cfloop>

            <!--- If it makes it this far without error, then we are ready to commit --->
            <cftransaction action="commit" />

            <cfcatch type="database">
                <!--- If we are in the <cfcatch></cfcatch> block then an error occured, let's roll back the changes --->
                <cftransaction action="rollback" />
                <cfreturn false />
            </cfcatch>
        </cftry>
        </cftransaction>
        
        <cfreturn true />
    </cffunction>

Modifying the deleteList() method to include child objects

We're essentially going to do the same thing with our deleteList() method. So I will not describe the process, but here is the final code (again, this code is untested):


    <cffunction name="deleteList" access="public" output="false" returntype="boolean">
        <cfargument name="list" type="model.todos.list" required="true" />
        <cfset var qDelete = "">
        <cfset var aItems = arguments.list.getItems() />
        
        <cftransaction action="begin">
        <cftry>
            <cfquery name="qDelete" datasource="#variables.dsn#">
                DELETE FROM    lists
                WHERE    listid = <cfqueryparam value="#arguments.list.getlistid()#" CFSQLType="cf_sql_integer" />
            </cfquery>

            <cfloop from="1" to="#ArrayLen(aItems)#" index="iItems">
                <cfset deleteItem(iItems) />
            </cfloop>
            
            <cftransaction action="commit" />
            
            <cfcatch type="database">
                <cftransaction action="rollback" />
                <cfreturn false />
            </cfcatch>
        </cftry>
        </cftransaction>
        <cfreturn true />
    </cffunction>

Conclusion

Our model is now ready. We can begin the next part of the application in my next few posts, where we can really start using ColdBox. We will probably still need to go in an add things to our model objects that we did not think of, and no doubt we will need to go in a fix minor bugs.

Thanks for being patient through all this set up.

Comments
Francois Levesque's Gravatar Hi Jason,

Great post, I'm really enjoying the series so far! I just wanted to point out a small typo where you list your 10 service methods:

createdList should be createList

Just a detail, but it proves I'm paying attention!
# Posted By Francois Levesque | 11/3/08 8:24 AM
Jason Dean's Gravatar @Francois, fixed. Thanks for the compliments and correction.
# Posted By Jason Dean | 11/3/08 8:38 AM
Doug's Gravatar Jason,
Since I'm new to CB and relatively new to cfc's (coming from CF5 era) maybe I'm not understanding something here. If I try introspection on the cfc's I get the error listed below. BUT, this is only when the cfc's are in the CB created directory structure. If I move the files to a new non-CB created directory introspection works as advertised. Any ideas?
Doug
Error including config file: Could not find the included template /todolist/model/todos/config/routes.cfm.
Note: If you wish to use an absolute template path (for example, template="/mypath/index.cfm") with CFINCLUDE, you must create a mapping for the path using the ColdFusion Administrator. Or, you can use per-application settings to specify mappings specific to this application by specifying a mappings struct to THIS.mappings in Application.cfc.
Using relative paths (for example, template="index.cfm" or template="../index.cfm") does not require the creation of any special mappings. It is therefore recommended that you use relative paths with CFINCLUDE whenever possible.

The error occurred in C:\Inetpub\wwwroot\coldbox\system\frameworkSupertype.cfc: line 282 yadda, yadda, yadda
# Posted By Doug | 11/16/08 8:58 PM
Doug's Gravatar I should mention I was trying to introspect todoGateway.cfc, but I get this error with any of the three cfc's.
D.
# Posted By Doug | 11/16/08 9:00 PM
Jason Dean's Gravatar @doug, I am not sure what method you are using to introspect the object. If I simply browse to the file http://todolists:81/model/todos/todogateway.cfc it seems to work fine for me. The ColdFusion component explorer comes up for me just fine.

I've seen that error before, but I am not sure in what context. I do know that it was not while browsing to the cfcs. Is this what you are trying to do? If not, what are you doing to introspect the cfcs?
# Posted By Jason Dean | 11/16/08 9:32 PM
Doug's Gravatar Jason,

I'm using http://localhost/todolist/model/todos/todoGateway..... I've never seen this either so I wonder if it is a CF Admin setting (debug?) causing the problem. The odd thing is that it ONLY happens IF ColdBox is involved.

I'll ask on the CB site and see what comes up.

Doug
# Posted By Doug | 11/17/08 11:31 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner