My favorites | Sign in
Project Logo
                
Search
for

NOTE: This wiki contains "bleeding edge" documentation, which may reflect a future version of Wheels. Entries in the wiki may also be incomplete and unedited. Please visit cfwheels.org/docs for the "official" documentation.

Introduction

Handling Requests With Controllers

Database Interaction Through Models

Displaying Views to Users

Working With Wheels

Contributing to Wheels

Sample Applications

The Wheels API

* Not scheduled for the 1.0 release.

Updated Nov 17, 2009 by ch...@clearcrystalmedia.com
Labels: chapter, 1.0, published_prod
ObjectValidation  
Wheels utilizes validation setup within the model to enforce appropriate data constraints and persistence. Validation may be performed for saves, creates, and updates.

Basic Setup

In order to establish the full cycle of validation, 3 elements need to be in place:

  1. Model file containing business logic for the database table. Example: models/User.cfc
  2. Controller file for creating, saving or updating a model instance. Example: controllers/Users.cfc
  3. View file for displaying the original data inputs and an error list. Example: views/users/index.cfm

Note: Saving, creating, and updating model objects can also be done from the model file itself (or even in the view file if you want to veer completely off into the wild). But to keep things simple, all examples in this chapter will revolve around code in the controller files.

The Model

Validations are always defined in the init() method of your model. This keeps everything nice and tidy because another developer can check init() to get a quick idea on how your model behaves.

Let's dive right into a somewhat comprehensive example:

<cfcomponent extends="Model" output="false">
  
    <cffunction name="init">
        <cfset validatesPresenceOf(properties="firstName,lastName,email,age,password")>
        <cfset validatesLengthOf(properties="firstName,lastName", maximum=50)>
        <cfset validatesUniquenessOf(property="email")>
        <cfset validatesNumericalityOf(property="age", onlyInteger=true)>
        <cfset validatesConfirmationOf(property="password")>
    </cffunction>

</cfcomponent>

This is fairly readable on its own, but this example defines the following rules that will be run before a create, update, or save is called:

If any of these validations fail, Wheels will not commit the create or update to the database. As you'll see later in this chapter, the controller should check for this and react accordingly by showing error messages generated by the model.

Allowing Blank Values

Sometimes you may want to allow an empty string or NULL value for a property but do want validation if a value is provided. In this case, you can use the allowBlank argument.

Let's say that we wanted to instead require lastName but allow a blank value for firstName. This is how the affected lines would appear in the above example:

<cfset validatesPresenceOf(properties="lastName,email,age,password")>
<cfset validatesLengthOf(property="firstName", maximum=50, allowBlank=true)>
<cfset validatesLengthOf(property="lastName", maximum=50)>

Now Wheels will only check for the maximum length on firstName if its value is provided.

Listing of Validation Functions

Wheels provides quite a few useful model validation functions. These functions remove much of the tedious drudgery that you're probably used to when writing data validation.

Use when, if, or unless to Limit the Scope of Validation

If you want to limit the scope of the validation, you have 3 arguments at your disposal: when, if, and unless.

when Argument

The when argument accepts 3 possible values.

  1. onSave (the default)
  2. onCreate
  3. onUpdate

To limit our email validation to run only on create, we would change that line to this:

<cfset validatesUniquenessOf(property="email", when="onCreate")>

if and unless Arguments

if and unless provide even more flexibility when the when argument isn't specific enough for your validation's needs.

Each argument accepts a string containing an expression to evaluate. if specifies when the validation should be run. unless specifies when the validation should not be run.

As an example, let's say that the model should only verify a CAPTCHA if the user is logged out, but not when they enter their name as "Ben Forta":

<cfset validate(method="validateCaptcha", if="not isLoggedIn()", unless="this.name is 'Ben Forta'")>

Custom Validations

At the end of the listing above are 3 custom validation functions: validate(), validateOnCreate(), and validateOnUpdate(). These functions allow you to create your own validation rules that aren't covered by Wheels's out-of-the-box functions.

There is only one difference between how the different functions work:

To use a custom validation, we pass one of these functions a method or set of methods to run:

<cfset validate(method="validateEmailFormat")>

We then should create a method called validateEmailFormat, which in this case would verify that the value set for this.email is in the proper format. If not, then the method sets an error message for that field using the addError() function.

<cffunction name="validateEmailFormat" access="private">
    <cfif not IsValid("email", this.email)>
        <cfset addError(property="email", message="Email address is not in a valid format.")>
    </cfif>
</cffunction>

Note that IsValid() is a function build into your CFML engine.

This is a simple rule, but you can surmise that this functionality can be used to do more complex validations as well. It's a great way to isolate complex validation rules into separate methods of your model.

Adding Errors to the Model Object as a Whole

We've mainly focused on adding error messages at a property level, which admittedly is what you'll be doing 80% of the time. But we can also add messages at the object level with the addErrorToBase() function.

As an example, here's a custom validation method that doesn't allow the user to sign up for an account between the hours of 3:00 and 4:00 am in the server's time zone:

<cffunction name="disallowMaintenanceWindowRegistrations" access="private">
    <cfset var loc = {}>
    <cfset loc.hourNow = DatePart("h", Now())>

    <cfif loc.hourNow gte 3 and loc.hourNow lt 4>
        <cfset loc.timeZone = CreateObject("java", "java.util.TimeZone").getDefault()>
        <cfset addErrorToBase(message="We're sorry, but we don't allow new registrations between the hours of 3:00 and 4:00 am #loc.timeZone#.")>
    </cfif>
</cffunction>

Sure, we could add logic to the view to also not show the registration form, but this validation in the model would make sure that data couldn't be posted via a script between those hours as well. Better safe than sorry if you're running a public-facing application!

The Controller

The controller continues with the simplicity of validation setup, and at the most basic level requires only 5 lines of code to persist the form data or return to the original form page to display the list of errors.

<cfcomponent extends="Controller" output="false">

    <cffunction name="save">
        <!--- User model from form fields via params --->
        <cfset newUser = model("user").new(params.newUser)>

        <!--- Persist new user --->
        <cfif newUser.save()>
            <cfset redirectTo(action="success")>
        <!--- Handle errors --->
        <cfelse>
            <cfset renderPage(action="index")>
        </cfif>
    </cffunction>

</cfcomponent>

The first line of the action creates a newUser based on the user model and the form inputs (via the params struct).

Now, to persist the object to the database, the model's save() call can be placed within a <cfif> test. If the save succeeds, the save() method will return true, and the contents of the <cfif> will be executed. But if any of the validations set up in the model fail, the save() method returns false, and the <cfelse> will execute.

The important step here is to recognize that the <cfelse> renders the original form input page using the renderPage() function. When this happens, the view will use the newUser object defined in our save() method. If a redirectTo() were used instead, the validation information loaded in our save() method would be lost.

The View

Wheels factors out much of the error display code that you'll ever need. As you can see by this quick example, it appears to mainly be a normal form. But when there are errors in the provided model, Wheels will apply styles to the erroneous fields.

<cfoutput>

#errorMessagesFor("newUser")#

#startFormTag(action="save")#
    #textField(label="First Name", objectName="newUser", property="nameFirst")#
    #textField(label="Last Name", objectName="newUser", property="nameLast")#
    #textField(label="Email", objectName="newUser", property="email")#
    #textField(label="Age", objectName="newUser", property="age")#
    #passwordField(label="Password", objectName="newUser", property="password")#
    #passwordField(label="Re-type Password to Confirm", objectName="newUser", property="passwordConfirmation")#
    #submitTag()#
#endFormTag()#

</cfoutput>

The biggest thing to note in this example is that a field called passwordConfirmation was provided so that the validatesConfirmationOf() validation in the model can be properly tested.

For more information on how this code behaves when there is an error, refer to the FormHelpersandShowingErrors chapter.

Error Messages

For your reference, here are the default error message formats for the different validation functions:

Function Format
validatesConfirmationOf [property] should match confirmation
validatesExclusionOf [property] is reserved
validatesFormatOf [property] is invalid
validatesInclusionOf [property] is not included in the list
validatesLengthOf [property] is the wrong length
validatesNumericalityOf [property] is not a number
validatesPresenceOf [property] can't be empty
validatesUniquenessOf [property] has already been taken

Custom Error Messages

Wheels models provide a set of sensible defaults for validation errors. But sometimes you may want to show something different than the default.

There are 2 ways to accomplish this: through global defaults in your config files or on a per-property basis.

Setting Global Defaults for Error Messages

Using basic global defaults for the validation functions, you can set error messages in your config file at config/settings.cfm.

<cfset set(functionName="validatesPresenceOf", message="Please provide a value for property")>

As you can see, you can inject the property's name by adding [[property]] to the message string. Wheels will automatically separate words based on your camelCasing of the variable names.

Setting an Error Message for a Specific Model Property

Another way of adding a custom error message is by going into an individual property in the model and adding an argument named message.

Here's a change that we may apply in the init() method of our model:

<cfset validatesNumericalityOf(property="email", message="Email address is already in use in another account")>

Sign in to add a comment
Hosted by Google Code