Reactor Validation Part III

Word Count: 89

In the final installment of our mini series we are getting down and dirty with custom validation. We learned in part II how basic validation is done. If you were to launch our application, go to add contact, and submit the form without entering any data you should see the following errors.

  • The fname field is required but was not provided
  • The lname field is required but was not provided
  • The emailaddress field is required but was not provided

So why did it choose these fields as required? If you look into our database schema you will noticed that I marked fname, lname, and emailaddress as not null. Reactor knows this and said at the very least I need these fields filled in (and within the data type and length requirements) to submit this form. At this point things are good because we have not written any validation code yet our form submission is being checked for us. First thing you probably noticed is that the error messages are reading the form field names. We want to make these error messages a little more descriptive. To accomplish this we will make some minor changes to our Dictionary file. The file contactdictionary.xml is located in your rvdata folder. Let us take a look at the 3 fields we are trying to update.

<fname>
         <label>fname</label>
         <comment/>
         <maxlength>50</maxlength>
         <notProvided>The fname field is required but was not provided.</notProvided>
         <invalidType>The fname field does not contain valid data. This field must be a string value.</invalidType>
         <invalidLength>The fname field is too long. This field must be no more than 50 bytes long.</invalidLength>
      </fname>
      <lname>
         <label>lname</label>
         <comment/>
         <maxlength>50</maxlength>
         <notProvided>The lname field is required but was not provided.</notProvided>
         <invalidType>The lname field does not contain valid data. This field must be a string value.</invalidType>
         <invalidLength>The lname field is too long. This field must be no more than 50 bytes long.</invalidLength>
      </lname>
      <emailaddress>
         <label>emailaddress</label>
         <comment/>
         <maxlength>50</maxlength>
         <notProvided>The emailaddress field is required but was not provided.</notProvided>
         <invalidType>The emailaddress field does not contain valid data. This field must be a string value.</invalidType>
         <invalidLength>The emailaddress field is too long. This field must be no more than 50 bytes long.</invalidLength>
      </emailaddress>

To update our error messages with a little bit more of a description we can simply update our xml file to look like this

<fname>
         <label>fname</label>
         <comment/>
         <maxlength>50</maxlength>
         <notProvided>The first name field is required but was not provided.</notProvided>
         <invalidType>The first name field does not contain valid data. This field must be a string value.</invalidType>
         <invalidLength>The first name field is too long. This field must be no more than 50 bytes long.</invalidLength>
      </fname>

Ok, so now our error messages look a lot better and we are validating that the user entered something in all three of the fields, but were not done yet. What if wanted to validate that what the user was entering in the email field was a properly formatted email address. If you look in your rvdata/Validator folder you have a file called contactValidator.cfc. If you open the file you will notice there is no code, what gives? This component is extending the base contactValidator.cfc located In reactor/project/ReactorValidation/Validator/contactValidator/. The extends keyword tell us what file to extend

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

   <!--- Place custom code here, it will not be overwritten --->
</cfcomponent>

The first thing we are doing here is overriding our validateEmailAddress method so we can add some custom code. We still want the original validation to be run, that will check to make sure something was entered and that it fits within the data type and length constraints. To accomplish this we will use the super keyword which calls the parent method. Now that basic validation has been done lets call our validate email format method. Next we instantiate our contactGateway and call our validation method, which sticking with best practices is business logic and located in our gateway. Finally if an error is found we add a new error to the errorCollection array.

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

   <!--- Place custom code here, it will not be overwritten --->
   
   <cffunction name="validateEmailAddress" access="public" hint="I validate the emailaddress field" output="false" returntype="reactor.util.ErrorCollection">
      <cfargument name="ContactRecord" required="false" type="reactor.project.ReactorValidaion.Record.ContactRecord"/>
      <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 super.validateEmailAddress(arguments.ContactRecord,arguments.ErrorCollection)>
      
      <!--- validate that the email adddress is formated correctly --->
      <cfset isValidEmail(arguments.ContactRecord,arguments.ErrorCollection)>
      
      <cfreturn arguments.ErrorCollection />
   </cffunction>
   
   <!--- validateEmailFormat --->
   <cffunction name="validateEmailFormat" access="public" hint="I validate that the users email address is correctly formated." output="false" returntype="reactor.util.ErrorCollection">
      <cfargument name="ContactRecord" required="false" type="reactor.project.ReactorValidaion.Record.ContactRecord"/>
      <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 ContactGateway = _getReactorFactory().createGateway("contact") />

      <!--- returns true if the email address is formated properly --->
      <cfif NOT ContactGateway.validateEmailAddress(arguments.ContactRecord.getEmailAddress())>
         <cfset arguments.ErrorCollection.addError("contct.emailaddress.format") />
      </cfif>
      
      <cfreturn arguments.ErrorCollection />
   </cffunction>
   
</cfcomponent>

The contact.emailaddress.format tells us what type of error. We can then go back to our dictorary file and add a custom message for that.

<emailaddress>
         <label>emailaddress</label>
         <comment/>
         <maxlength>50</maxlength>
         <notProvided>The emailaddress field is required but was not provided.</notProvided>
         <invalidType>The emailaddress field does not contain valid data. This field must be a string value.</invalidType>
         <invalidLength>The emailaddress field is too long. This field must be no more than 50 bytes long.</invalidLength>
         <format>The email address field is not properly formatted.</format>
      </emailaddress>

Last but not least we will extend our contactGateway and add our method that checks to make sure the address is properly formatted.

<cfcomponent hint="I am the database agnostic custom Gateway object for the contact object. I am generated, but not overwritten if I exist. You are safe to edit me."
   extends="reactor.project.ReactorValidation.Gateway.contactGateway" >

   <!--- Place custom code here, it will not be overwritten --->
   
   <cffunction name="validateEmailAddress" access="public" output="false">
      <cfargument name="email" type="string" required="true"/>
      <cfset var str = arguments.email>
      <cfscript>
      /**
       * Tests passed value to see if it is a valid e-mail address (supports subdomain nesting and new top-level domains).
       * Update by David Kearns to support '
       * SBrown@xacting.com pointing out regex still wasn't accepting ' correctly.
       * More TLDs
       * Version 4 by P Farrel, supports limits on u/h
       *
       * @param str     The string to check. (Required)
       * @return Returns a boolean.
       * @author Jeff Guillaume (jeff@kazoomis.com)
       * @version 4, December 30, 2005
       */
       return (REFindNoCase("^['_a-z0-9-]+(\.['_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.(([a-z]{2,3})|(aero|coop|info|museum|name|jobs|travel))$",str) AND len(listGetAt(str, 1, "@")) LTE 64 AND len(listGetAt(str, 2, "@")) LTE 255) IS 1;
      </cfscript>

   </cffunction>
</cfcomponent>

If you try and submit the form now you will see another error and it should say the email address field is not properly formatted. So there you have, you can now do basic validation as well as adding some custom validation. I hope these short tutorials have helped you out, please drop me a line if they did or what you would like to see next. If I made any mistakes please drop me a line as well, I am tired and flying through the end of this. This may seem like a lot at first but don't worry, it is easy once you get the hang of it.


Comments

#1 Posted By: Brian Kotek Posted On: 7/27/06 4:05 AM
A regex validation of an email address absolutely does not belong in a Gateway. I would just do this directly in the Validator, or create a separate and reusable EmailValidator that gets instantiated and used from within the Validator. Gateways are specifically meant to abstract database interaction.
#2 Posted By: dan Posted On: 7/27/06 9:39 AM |
Author Comment
Brian,
You make a great point. I may write an update that checks to see if the email address has been entered in our system yet.
#3 Posted By: Julian Halliwell Posted On: 8/21/06 5:15 AM
For CF7: isValid("email",xxx)
#4 Posted By: Richard Davies Posted On: 2/6/07 7:18 PM
Isn't the line

<cfset isValidEmail(arguments.ContactRecord,arguments.ErrorCollection)>

supposed to actually call the validateEmailFormat() function? If not, where is isValidEmail() defined?
#5 Posted By: Dan Posted On: 2/6/07 7:21 PM |
Author Comment
Richard,
You are correct. I may have moved some methods around and changed some names but you get the idea.
#6 Posted By: Thomas Messier Posted On: 3/5/07 6:18 PM
Hey Dan, I'm looking through your tutorials and trying to apply it, but I can't seem to find any dictionary files. Has there been any change the Reactor, or am I missing something?


Post Your Comment







Show Captcha

If you subscribe, any new posts to this thread will be sent to your email address.

Copyright © 2007 Dan Vega | BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.