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, 2013 >>
SMTWTFS
1234
567891011
12131415161718
19202122232425
262728293031
Search Blog

Recent Comments
Re: Disappearing IE Popup Window During Save/Open Dialog (by LZ at 4/20 7:58 AM)
Re: Create Dynamic WHERE Clauses in PHP (by pooja at 3/20 7:29 AM)
Re: Just What IS a 'Service Layer', Anyway? (by EugenK at 3/07 7:56 PM)
Re: Using Google as your CF Mail Server (by 5starwebteam.com at 2/25 1:27 AM)
Re: Why Provide for Service layer objects in CFWheels? (by Steven Benjamin at 1/25 11:43 AM)
Re: What is an 'Advanced' Coldfusion Developer? (by ColdFusion Developer at 12/24 5:14 AM)
Re: Equivalent of SQL "TOP X" in Oracle (by Ashenafi Desalegn at 12/06 5:29 AM)
Re: PHP Export to Excel Snippet (by serene at 12/05 1:44 AM)
Re: Just What Is 'Application Logic', Anyway? (by Arif at 11/13 8:06 AM)
Re: Hosts File Changes Not Acknowledged on Vista 64 (by Aaron at 10/22 2:31 PM)
Re: PHP Export to Excel Snippet (by Jafar Shah at 10/10 4:28 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Chenelle S at 10/04 12:53 PM)
Re: PHP Export to Excel Snippet (by Kilo at 9/26 5:20 PM)
Re: Porting Coldfusion Code to Mura (by tariq at 9/03 9:51 AM)
Re: Just What IS a 'Service Layer', Anyway? (by James at 8/27 4:06 PM)
Re: Calculating Business Hours (by helen at 8/14 2:54 AM)
Re: What IS 'Business Logic', Anyway? (by dougboude at 8/06 11:30 AM)
Re: What IS 'Business Logic', Anyway? (by Adrianne at 8/06 10:29 AM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by dougboude at 8/04 4:39 PM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by Lola LB at 8/04 7:43 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
Subscription Options

You are not logged in, so your subscription status for this entry is unknown. You can login or register here.

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Nice article! One thing I really like about Reactor is the validation layer and just how insanely easy it is to extend.

Also, is there a reason for not using reactor to do the query to check if email is unique? I think I ran into the same thing and it did seem slightly faster that way, even at the expense of SQL.

For the password confirmation, how are you making sure it's the same? I mean when the user object is edited I don't think the password confirmation will be provided. I suppose you could manually set the confirmation one when you edit the user, and when the user edits their own profile they'd be required to give password/confirmation at some points? Password confirmation isn't in the DB of course, but I guess you added a method over on the extended user object for it?
Posted by Adam Fortuan on May 25, 2007 at 9:35 AM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Hi Adam.

As far as not using reactor to do the check for duplicate emails, Reactor-speak isn't one of my stronger points (I have 'invested' countless days trying to use it exclusively, and always come out with a permanent frown) and due to me being in a hurry to just get it done, I resorted to puro sql. I don't typically use Reactor to abstract my SQL...I've just found it WAY too cumbersome and expensive, IO-wise. BUT, I do leverage it for its validation and the generic MG events that tie into it.

Regarding how i double check the password...I kinda cheated :). I added another field to my user table called "validatepassword" in order for it to be part of my user record. Then in my method I can just compare the value of the "password" and "validatepassword" fields.

Doug :0)
Posted by dougboude on May 25, 2007 at 10:04 AM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Doug, very nice summary. One thing I would do though (as Adam mentioned) is not have database query logic in the Validator. Just grab an instance of the proper Reactor gateways and check for dups either using the gateway methods (in this case it's a simple getByFields call) or using a custom Gateway method. In either case, keep the queries where they belong, in DAOs or Gateways, not in the Validator. Otherwise, great writeup!
Posted by Brian Kotek on May 25, 2007 at 1:23 PM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Thanks for the input Brian! I modified my code snippet to utilize a Reactor gateway rather than straight sql.
Posted by dougboude on May 25, 2007 at 3:12 PM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
I would probably go one step further and make the custom validation events more self-contained. In other words, don't pass in the UserRecord to validateEmail(), just pass the email value as the first argument, and add a default to the second argument (the ErrorCollection) to create it if it isn't passed (just like the validate() method does). What does this gain you? You can now validate the unique email by itself if you want to just by calling validateEmail() on your Validator and passing in the email address to be validated. This is great for an AJAX system for example, where you might run an AJAX request in the background when they enter their email address. You can still validate as part of a full UserRecord validation too.

Unfortunately, the generated validation methods don't do this (they require the UserRecord), but it would be a very simple fix to Reactor to have it pass in the actual values instead of the whole Record to the validation methods. I may run this past Doug and add an enhancement request. Then you'd be able to call either your custom validation methods or the generated validation methods one by one OR on a whole Record.
Posted by Brian Kotek on May 25, 2007 at 4:07 PM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Doug, Thanks for the info here, it really helped me solve what first appeared to be a frustrating problem. However I was also curious how to validate the password without "cheating" and adding the second field to the database. I have documented my results here:

http://www.tmaguire.net/blog/index.cfm/2007/12/1/Custom-Validation-with-Reactor-and-ModelGlue-confirm-your-password
Posted by Tim on December 1, 2007 at 9:13 PM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Hey doug, have you done validations using transfer orm? If so can you post an example..
Posted by udip on March 24, 2008 at 1:47 PM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Hello Udip.
Sorry, but I have no Transfer examples to post. As of late, I have become extremely ANTI-ORM, so I'm not even using Reactor anymore; for me, an ORM adds FAR too much overhead in terms of time, extra complexity, and learning curve, not to mention slowing down my apps. These days I write my code "commando style", sans ORM, and have zero regrets.
Posted by dougboude on March 26, 2008 at 9:44 AM

Re: Custom Validation with Generic Commit: a Model-Glue Case Study
Doug,
Can you elaborate on why you've abandoned ORM? I browsed thru your blog but didn't see any references to this decision. I'm wrestling with its costs vs benefits as well and would be interested in hearing from someone further down that path.

Thanks,
Dave
Posted by dave jones on April 11, 2008 at 9:15 PM

Name:   Required
Email:   Required your email address will not be publicly displayed.

Want to receive notifications when new comments are added? Login/Register for an account.

Time to take the Turing Test!!!

Three plus Zero equals
Type in the answer to the question you see above:

Your comment:

Sorry, no HTML allowed!