Categories
Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Web Hosting

<< May, 2007 >>
SMTWTFS
12345
6789101112
13141516171819
20212223242526
2728293031
Search Blog

Recent Comments
Re: Equivalent of SQL "TOP X" in Oracle (by Mark Foster at 7/07 4:04 PM)
Re: SQL Forward Engineering with Visio 2003 Professional (by Thomas at 6/26 4:41 AM)
Re: One Shot Query to Recalculate Orderby Field - MySQL (by gary at 6/17 6:46 PM)
Re: DON'T GET SICK IN ARKANSAS! (by r. wood at 5/25 12:00 AM)
Re: SQL Forward Engineering with Visio 2003 Professional (by Andrew at 4/30 6:14 AM)
Re: Basic Ajax Select List Filter in PHP (by good at 2/04 5:26 AM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by swalker at 2/03 2:15 AM)
Re: Approaches to Building Strings: The Imploding Array (by bantal silikon at 2/01 9:44 PM)
Re: Disappearing IE Popup Window During Save/Open Dialog (by AddisonDean at 1/15 9:59 AM)
Re: My Top 20 Life Lessons for Boys and Young Men (by Alex at 1/13 8:45 PM)
Re: Array Loop Modifications in CFSCRIPT (by Alex at 11/25 11:18 AM)
Re: Array Loop Modifications in CFSCRIPT (by Abram at 11/14 11:32 PM)
Re: Recursive Functions in ColdFusion (by Dwayne at 10/25 3:47 PM)
Re: Porting Coldfusion Code to Mura (by dh at 10/16 10:14 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Devil May Cry at 9/26 1:38 AM)
Re: Why I Hate ORMs (a solicited rant) (by guideX at 9/12 11:38 PM)
Re: Recursive Functions in ColdFusion (by KP at 8/08 7:13 PM)
Re: American Airlines, YOU SUCK! (by Alison at 7/23 4:48 PM)
Re: SQL Forward Engineering with Visio 2003 Professional (by Harshad at 7/11 9:17 AM)
Re: Disappearing IE Popup Window During Save/Open Dialog (by LZ at 4/20 7:58 AM)
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

25 May 2007
Custom Validation with Generic Commit: a Model-Glue Case Study
Someone recently asked about how to specifically ensure that a submitted email address is unique when using a model-glue generic commit, so I thought I'd share an example since I recently had to do that very thing.

I'm assuming for the remainder of this post that the reader is already familiar with Model-Glue in a practical sense, and at least knows of the existence of Reactor's automagic validation. Still, I'll try not to leave out too many relevant details.

Okay, the scenario:

I have a secured app, and I want to give new users the opportunity to sign up for an account. I'm using email address as the user name since in theory it should always be unique to an individual. (note: I opted NOT to set up my user table so that the email address field has a unique index on it) So, the user clicks "Sign me up!", I present them with a form to fill out, one field being their email address. They submit the form, and here's where we dive down under the waves to see what's happening...


The form submits to the event "inspector.create", which in the modelglue.xml file reads as follows:
<event-handler name="inspector.create">
    <broadcasts>
        <message name="ModelGlue.genericCommit">
            <argument name="recordName" value="UserRecord" />
            <argument name="criteria" value="" />
            <argument name="object" value="User" />
            <argument name="validationName" value="UserValidation" />
        </message>
    </broadcasts>
    <views></views>
    <results>
        <result name="commit" do="inspector.newuser" redirect="true" append="email" preserveState="false" />
        <result name="validationError" do="inspector.signup" redirect="false" append="" preserveState="true" />
    </results>
</event-handler>

Notice we're using a generic commit to handle this, which works because all of the needed data resides within form fields that are named exactly as their database field counterparts (eg; in my table there's a field called 'email', in my form there's a form field named 'email', etc.).
Now, the fact that we have specified an argument named "validationName" in our generic commit means that before the form data is committed to the database, Reactor is going to invoke the aid of one of the CFCs it auto-generated for us in order to "validate" the info submitted. By default, validation consists of checks that were created based on your table's metadata (unique indexes, datatypes, null not allowed, etc.), but Reactor was kind enough to provide us a convenient place to extend and customize that default validation if we so desire. In my scenario, since I did NOT choose to put a rule in place specifying that my email field should be unique, I added a custom method to perform that check.

To locate the CFC for adding custom validation, look in \model\data\reactor\Validator\, and find the cfc named after your target table. In my case, it's UserValidator.cfc

By default, the guts of the Validator CFCs you'll be working with look similar to the following:

<cfcomponent hint="I am the validator object for the Section object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
    extends="reactor.project.myprojectname.Validator.SectionValidator">
    <!--- Place custom code here, it will not be overwritten --->
</cfcomponent>

I decided to add two more methods of validation to my user object:
  1. make sure they typed their password in twice the same way,
  2. and make sure their email isn't already used in the system
Ah, but when you look at the content of my CFC, you'll see there are THREE methods present, and NOT just two. Take a gander:

<cfcomponent hint="I am the validator object for the User object.  I am generated, but not overwritten if I exist.  You are safe to edit me."
    extends="reactor.project.myprojectname.Validator.UserValidator">

    <CFFUNCTION name="validate" access="public" hint="I validate an  record" output="false" returntype="any" _returntype="reactor.util.ErrorCollection">
        <cfargument name="UserRecord" hint="I am the Record to validate." required="no" type="any" _type="reactor.project.housefacks.Record.UserRecord" />
        <cfargument name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="any" _type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <CFSET validatePasswordVals(arguments.UserRecord, arguments.ErrorCollection) />
        <CFSET validateEmail(arguments.UserRecord, arguments.ErrorCollection) />
        <CFSET super.validate(arguments.UserRecord, arguments.ErrorCollection) />
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
   
    <CFFUNCTION name="validatePasswordVals" access="public"  output="false" returntype="reactor.util.ErrorCollection">
        <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" />
        <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <!--Blue is only allowed as a selection for Sky for people  whose weathercode = 5-->
        <CFIF  arguments.UserRecord.getPassword() is not arguments.UserRecord.getConfirmPassword() >
            <CFSET arguments.ErrorCollection.addError("user.password.notconfirmed") />
        </CFIF>
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
   
    <CFFUNCTION name="validateEmail" access="public"  output="false" returntype="reactor.util.ErrorCollection">
        <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" />
        <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" />
        <cfset var userGateway = "" />
        <CFIF  arguments.UserRecord.getEmail() is not "" >
            <cfset userGateway = reactorfactory.createGateway("user").getByFields(email=arguments.UserRecord.getEmail()) />
            <cfif userGateway.recordcount IS NOT 0>
                <CFSET arguments.ErrorCollection.addError("user.email.alreadyexists") />
            </cfif>
        </CFIF>
        <CFRETURN arguments.ErrorCollection />
    </CFFUNCTION>
</cfcomponent>


The method present here that you may not have anticipated is called "validate", and is the exact same name as the method you would find in Reactor's core user validation object. So, what have we in effect done, boys and girls? That's right! We have (choose your favorite word, they both mean the same thing) overloaded/overriden the main "validate" method, in order to ensure that not only the original, auto-generated validation methods get called, but also the two new ones we added after the fact.

Let's take a closer look at our version of the "validate" method (this will only take a second, there are a couple of important things to note in there).

First off, you'll note that every validation method requires two arguments: an incoming record whose values are being validated, and the errorcollection where we (dang, this makes too much sense!) collect our errors.
Second, notice that we are FIRST executing our custom validation methods, then afterwards executing the auto-generated "validate" method by calling the object we extended, directly, via a call to "SUPER". Very cool, eh? Even though we initially overloaded our validate method in order to ensure that it got called rather than the core version of it, we were STILL able to call the original version as well. (By the way, that is a little trick I learned from Doug Sims' blog www.evenamonkey.com).

Alright then, we have submitted our form, used generic commit to perform validation, that validation called our extended object, executed the local custom methods first, then the system validate method. If any errors were encountered, our generic commit would have added a result named "ValidationError" to the event bucket (see the modelglue.xml snippet above), thus redirecting us back to the original page (where we have code in place looking for the presence of the error collection). If no errors were encountered, we're directed forward to the next event in the chain, and all is well.


One Mo Thang

Ah, one last thing that is of great importance to be aware of regarding Reactor validation: The Dictionary. The dictionary is an xml file that is specific to a validation object. In our example, since we have a userValidator object, there also exists a \model\data\reactor\Dictionary\userdictionary.xml file. This file is used to look up and translate any errors encountered so that the user is presented with readable text rather than a cryptic message. When you add custom validation methods, you also need to add dictionary entries. Consider the following snippet from my userdictionary.xml file:
<?xml version="1.0" encoding="UTF-8"?>
    <User>
        <email>
            <label>email</label>
            <comment/>
            <maxlength>100</maxlength>
            <scale>0</scale>
            <invalidType>The email field does not contain valid data.  This field must be a string value.</invalidType>
            <invalidLength>The email field is too long.  This field must be no more than 100 bytes long.</invalidLength>
            <notProvided>The email field is required but was not provided.</notProvided>
            <alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists>
        </email>
        <password>
            <label>password</label>
            <comment/>
            <maxlength>50</maxlength>
            <scale>0</scale>
            <invalidType>The password field does not contain valid data.  This field must be a string value.</invalidType>
            <invalidLength>The password field is too long.  This field must be no more than 50 bytes long.</invalidLength>
            <notProvided>The password field is required but was not provided.</notProvided>
            <notconfirmed>The password you typed in is not the same as the "confirm password" value.</notconfirmed>
        </password>
    </User>

Look back at the custom method "validateEmail" we added earlier, and notice that if our validation fails, we're adding an error that looks like

<CFSET arguments.ErrorCollection.addError("user.email.alreadyexists") />


The syntax of that message is no coincidence...it's the same syntax you would use to access an item in an XML file. Fancy that! 'user' denotes the user dictionary; 'email' denotes the particular table field; and 'alreadyexists' is a term I just made up, and indicates that Reactor should look for a tag called 'alreadyexists' in order to find the correct translation for this error. Thus, you'll notice the tag

<alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists>

in the <email> section of our dictionary file.


To Sum it all up!

Okay, so in a nutshell, if you have a form being submitted and you want to ensure that the email address is unique (AND you haven't put a rule in place within the database itself so specifying this):

  1. add a method to your customizable validator CFC for the target table;
  2. add a 'validate' method to the same CFC in order to overload the system version of the same;
  3. within your custom 'validate' method, execute your custom method first
  4. within the same, execute the system version of validate using "SUPER.Validate()"
  5. edit your dictionary file to add a translation for your new custom error

That's it!

Doug out.
Posted by dougboude at 1:30 AM | PRINT THIS POST! | Link | 9 comments