Wheels utilizes validation setup within the model to enforce appropriate data constraints and persistence. Validation may be performed for saves, creates, and updates.
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: models/User.cfc
Controller file for creating, saving or updating a model instance. Example: controllers/Users.cfc
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.
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:
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
, and password
fields must be provided, and they can't be blank.
At maximum, firstName
and lastName
can each be up to 50 characters long.
The value provided for email
cannot already be used in the database.
The value for age
can only be an integer.
password
must be provided twice, the second time via a field called passwordConfirmation
.
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.
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 NULL
will 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:
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.
If you want to limit the scope of the validation, you have 3 arguments at your disposal: when
, condition
, and unless
.
The when
argument accepts 3 possible values.
onSave
(the default)
onCreate
onUpdate
To limit our email validation to run only on create, we would change that line to this:
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":
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:
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.
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.
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:
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 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.
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.
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.
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 Errorschapter.
For your reference, here are the default error message formats for the different validation functions:
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
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.
Using basic global defaults for the validation functions, you can set error messages in your config file at config/settings.cfm
.
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.
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: