It seems quite common in the ColdFusion community to see objects with a validate() function. This is used to handle when your object contains potentially invalid data, such as invalid data supplied from a form.
This leads to a good question; should your objects be allowed to have an invalid state? Let’s consider an example where you might have a simple user object which has an first name, last name and date of birth.
Let’s suppose that for this object to be valid we have the following rules:
the first name and last name are required
the birth date is optional, but if supplied it should have a value not more than 80 years ago.
Scenario 1: Invalid is OK, Dude, it all gets sorted in the end
Suppose you are creating a new User and have a simple user form. When your user form is submitted, you may have some code like this:
<!--- Create and populate our user ---> <cfset user = createObject("component","User").init()> <cfset user.setFirstName(form.firstName)> <cfset user.setLastName(form.lastName)> <cfset user.setDateOfBirth(form.dob)> <!--- Validate the object and handle any errors ---> <cfset errors = user.validate()> <cfif arrayLen(errors) eq 0> <!--- All good, so on to the next thing ---> <cfelse> <!--- Otherwise, handle the errors ---> </cfif>
So in this example, our user object is potentially in an invalid state, but a quick validate() lets us know if we are home free or if our user needs a little more attention.
Some things to note:
- We can set any values at all in our user object
- Validation is done within the object which returns an array of errors.
Scenario 2: Not OK, really really not OK
So what’s the problem with the code above? Looks good to me. Well let’s consider an alternative approach:
<!--- First validate our data, various options on how this might be done. ---> ... some validation and other stuff happens here ... <!--- Only create our object if there are no errors ---> <cfif arrayLen(errors) eq 0> <cfset user = createObject("component","User").init(firstName, lastName, dob)> <!--- All good, so on to the next thing, e.g. saving our user ---> <cfelse> <!--- Otherwise, handle the errors ---> </cfif>
Couple of things to note:
So in this example, the user object will only exist in a valid state. It cannot exist with invalid data.
When a User might not be a User …
In the first scenario, our object can contain ANY data at all, in particular it can contain ANY data our trusty users may like to enter. Suppose I handed you one of these user objects. You cannot assume that it is valid, because there is nothing to stop it from being invalid. Just to be sure, you might need to call validate() wherever you need to ensure the object data was OK to use.
Looking a little further, it seems to me that our User object is really just a “objectified” version of our form data. We let any data go into our object, then we ask the object to validate itself.
Our User object may have lots of behaviour, and that helps us to think this is a good useful object, but something doesn’t seem right.
So perhaps what we are calling a User object is not really a “User” at all. Perhaps our object is really a “UserForm” object. Maybe that makes more sense – forms need to be validated, so userForm.validate() might be much better that user.validate().
This is an important distinction, so let’s redefine our objects:
User: Is a business object and always exist in a valid state. Whenever you have a user object you know it is completely valid.
UserForm: Is a representation of a form. It can validate your form data and transform your form data into a real live business object.
Giving our UserForm a spin …
So if we are dealing with a UserForm now, then what should our code look like? It could look something like this:
<!--- Create our UserForm object and populate with the form data ---> <cfset userForm = createObject("component","UserForm").init()> <cfset userForm.populate(form)> <!--- The validation occurs when you call a function called hasErrors() ---> <cfif not userForm.hasErrors()> <!--- All good, so now we can get a nice clean User object ---> <cfset user = userForm.getUserBean()> <!--- Nice function! ---> <cfelse> <!--- Oh, errors here, so handle them ---> <cfset errors = userForm.getErrors()> ... display our form again here with errors ... </cfif>
So in this example, the user form objects knows how to create a user object based on valid form data.
So should your objects be allowed to have an invalid state?
It seems to me that “business objects” (or “beans”) that can exist in an invalid state are really just wrappers for potentially invalid user supplied data.
This same scenario can happen when importing data from an external file. We need to import the data into a “temporary” location first, then validate and only move to its final storage location (in a bean or in the database) only after the data is validated successfully.
Objects that are permitted to exist in an invalid state also seem less useful. In our example above, we know that the users name will absolutely always be a non empty string and the date will either be an empty string or a real date object (rather than a date string, for example). This knowledge gives us complete confidence in using our object in any scenario with no surprises.
What do you think?