Published on

Some notes about wheels by coldfusion

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total Views
On Slideshare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide


  1. 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. 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. 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. 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. 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:
  6. 6. <cfset validatesNumericalityOf(property="email", message="Email address is already in use in another account")>
  7. 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. 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