Database Interaction Through Models
Object Validation
Object Validation
Section titled “Object Validation”Basic Setup
Section titled “Basic Setup”In order to establish the full cycle of validation, 3 elements need to be in place:
- Model file containing business logic for the database table. Example:
app/models/User.cfc - Controller file for creating, saving or updating a model instance. Example:
app/controllers/Users.cfc - View file for displaying the original data inputs and an error list. Example:
app/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
Section titled “The Model”Validations are always defined in the config() method of your model. This keeps everything nice and tidy because another developer can check config() to get a quick idea on how your model behaves.
Let’s dive right into a somewhat comprehensive example:
component extends="Model" output="false" {
function config() { validatesPresenceOf( properties="firstName,lastName,email,age,password" ); validatesLengthOf(properties="firstName,lastName", maximum=50); validatesUniquenessOf(property="email"); validatesNumericalityOf(property="age", onlyInteger=true); validatesConfirmationOf(property="password"); }
}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:
- The
firstName,lastName,email,age, andpasswordfields must be provided, and they can’t be blank. - At maximum,
firstNameandlastNamecan each be up to 50 characters long. - The value provided for
emailcannot already be used in the database. - The value for
agecan only be an integer. passwordmust be provided twice, the second time via a field calledpasswordConfirmation.
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.
Listing of Validation Functions
Section titled “Listing of Validation Functions”- validatesConfirmationOf()
- validatesExclusionOf()
- validatesFormatOf()
- validatesInclusionOf()
- validatesLengthOf()
- validatesNumericalityOf()
- validatesPresenceOf()
- validatesUniquenessOf()
- validate()
- validateOnCreate()
- validateOnUpdate()
Automatic Validations
Section titled “Automatic Validations”Now that you have a good understanding of how validations work in the model, here is a piece of good news. By default, Wheels will perform many of these validations for you based on how you have your fields set up in the database.
By default, these validations will run without your needing to set up anything in the model:
- Fields set to
NOT NULLwill automatically trigger validatesPresenceOf(). - Numeric fields will automatically trigger validatesNumericalityOf().
- Date or time fields will be checked for the appropriate format.
- Fields that have a maximum length will automatically trigger validatesLengthOf().
Note these extra behaviors as well:
- Automatic validations will not run for Automatic Time Stamps.
- If you’ve already set a validation on a particular property in your model, the automatic validations will be overridden by your settings.
- If your database column provides a default value for a given field, Wheels will not enforce a validatesPresenceOf()rule on that property.
To disable automatic validations in your Wheels application, change this setting in /config/settings.cfm:
set(automaticValidations=false);You can also turn on or off the automatic validations on a per model basis by calling the automaticValidations() method from a model’s config() method.
See the chapter on Configuration and Defaults for more information on available Wheels ORM settings.
Use when, condition, or unless to Limit the Scope of Validation
Section titled “Use when, condition, 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, condition, and unless.
when Argument
Section titled “when Argument”The when argument accepts 3 possible values.
onSave(the default)onCreateonUpdate
To limit our email validation to run only on create, we would change that line to this:
validatesUniquenessOf(property="email", when="onCreate");condition and unless Arguments
Section titled “condition and unless Arguments”condition 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. condition 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”:
validate( method="validateCaptcha", condition="not isLoggedIn()", unless="this.name is 'Ben Forta'");Custom Validations
Section titled “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:
- validate() runs on the save event, which happens on both create and update.
- validateOnCreate() runs on create.
- validateOnUpdate() runs on update.
To use a custom validation, we pass one of these functions a method or set of methods to run:
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.
private function validateEmailFormat() { if ( !IsValid("email", this.email) ) { addError(property="email", message="Email address is not in a valid format."); }}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
Section titled “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 model 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:
private function disallowMaintenanceWindowRegistrations() { local.hourNow = DatePart("h", Now()); if ( local.hourNow >= 3 && local.hourNow < 4 ) { local.timeZone = CreateObject("java", "java.util.TimeZone").getDefault(); addErrorToBase( message="We're sorry, but we don't allow new registrations between the hours of 3:00 && 4:00 am #local.timeZone#." ); }}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
Section titled “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.
component extends="Controller" {
public function save() { // User model from form fields via params newUser = model("user").new(params.newUser); // Persist new user if ( newUser.save() ) { redirectTo(action="success"); // Handle errors } else { renderView(action="index"); } }
}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 renderView() 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
Section titled “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 Form Helpers and Showing Errors chapter.
Error Messages
Section titled “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
Section titled “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
Section titled “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.
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
Section titled “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 config() method of our model:
validatesUniquenessOf( property="email", message="Email address is already in use in another account");