The document summarizes validation in Wheels, an open-source Model-View-Controller framework for ColdFusion. It discusses setting up validation with three elements: a model file containing business logic, a controller file for creating/saving/updating models, and a view file for displaying inputs and errors. Validation rules are defined in the model's init() method using functions like validatesLengthOf(), validatesPresenceOf(), and custom validation methods. The controller checks if the model saves successfully and either redirects on success or re-renders the form with errors. The view displays errors using helper functions.
The 7 Things I Know About Cyber Security After 25 Years | April 2024
Wheels
1. Object Validation
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:
• 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.
2. 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
• validatesConfirmationOf()
• validatesExclusionOf()
• validatesFormatOf()
• validatesInclusionOf()
• validatesLengthOf()
• validatesNumericalityOf()
• validatesPresenceOf()
• validatesUniquenessOf()
• validate()
• validateOnCreate()
• validateOnUpdate()
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.
• onSave (the default)
• onCreate
• 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
3. 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:
<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 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:
<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!
4. 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()#
5. #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
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:
7. validatesLengthOf()
Description
Validates that the value of the specified property matches the length requirements supplied. Use the
exactly, maximum, minimum and within arguments to specify the length requirements.
Function Syntax
validatesLengthOf([ properties, message, when, allowBlank, exactly, maximum,
minimum, within, if, unless ])
Parameters
Parameter Type Required Default Description
properties string No Name of property or list of property names to
validate against (can also be called with the
property argument).
message string No [property] is Supply a custom error message here to override
the wrong
the built-in one.
length
when string No onSave Pass in onCreate or onUpdate to limit when
this validation occurs (by default validation
will occur on both create and update, i.e.
onSave).
allowBlank boolean No false If set to true, validation will be skipped if the
property value is an empty string or doesn't
exist at all.
exactly numeric No 0 The exact length that the property value has to
be.
maximum numeric No 0 The maximum length that the property value
has to be.
minimum numeric No 0 The minimum length that the property value
has to be.
within string No A list of two values (minimum and maximum)
that the length of the property value has to fall
within.
if string No String expression to be evaluated that decides
if validation will be run (if the expression
returns true validation will run).
unless string No String expression to be evaluated that decides
if validation will be run (if the expression
returns false validation will run).
Examples
<!--- Make sure that the `firstname` and `lastName` properties are not more than
50 characters and use square brackets to dynamically insert the property name
8. when the error message is displayed to the user (the `firstName` property will
be displayed as "first name") --->
<cfset validatesLengthOf(properties="firstName,lastName", maximum=50,
message="Please shorten your [property] please, 50 characters is the maximum
length allowed.")>
<!--- Make sure that the `password` property is between 4 and 15 characters --->
<cfset validatesLengthOf(property="password", within="4,20", message="The
password length has to be between 4 and