http://www.developer.com/

Back to article

Advanced Active Record Validations with Rails


November 26, 2007

In the first installment of this two-part series, you examined the data validation features available within the Rails framework's Active Record implementation. Among other tasks, you learned how to use built-in methods such as validates_presence_of() and validates_length_of() to place constraints on user-supplied data and notify the user if these constraints are violated. Doing so goes a long way towards ensuring not only the validity of your data, but also helps to negate attempts to surreptitiously insert malicious data into the application.

In this second installment, I'll introduce you to Active Record's validation callbacks. These callbacks allow you to perform even more powerful operations both prior to and following validation. As a basis for the examples found in this article, you'll continue using the Question model created in the previous article.

I'll close the article with an answer to a reader's question regarding how to remove attribute names from the beginning of custom validation error messages.

To refresh your memory, the corresponding questions MySQL table schema looks like this:

+--------------+------------+----+---+-------+--------------+
|Field         |Type        |Null|Key|Default|Extra         |
+--------------+------------+----+---+-------+--------------+
|id            |int(11)     |NO  |PRI|NULL   |auto_increment|
|name          |varchar(255)|NO  |   |       |              |
|email         |varchar(255)|NO  |   |       |              |
|phone         |varchar(255)|NO  |   |       |              |
|contact_method|varchar(255)|NO  |   |       |              |
|message       |text        |NO  |   |       |              |
+--------------+------------+----+---+-------+--------------+

Active Record Validation Callbacks

Active Record comes equipped with six validation-specific callbacks that you can use to perform a wide variety of cleanup operations. These callbacks allow you to execute various procedures at predefined points as specified by the callback. I'll define each of the callbacks here, and then you'll work through several practical examples illustrating their use:

Callback When It Executes
after_validation() After all validation procedures are complete
after_validation_on_create() After validation of new objects
after_validation_on_update() After validation of existing objects being updated
before_validation() Before the object is validated
before_validation_on_create() Before validation of new objects
before_validation_on_update() Before validation of existing objects being updated

Although I won't use them within any examples, a number of other useful callbacks are also available, some of which include:

Callback When It Executes
before_create() Before the object is added to the database
before_destroy() Before an object is destroyed
before_save() Before an object is saved to the database, either by way of creation or an update
before_update() Before an object is modified in the database

Be sure to weigh the meaning of each callback carefully, because of the subtle differences. For instance, if you're only interested in executing a certain procedure before validation of new database objects, you should use before_validation_on_create() instead of before_validation(), because the latter will trigger prior to validation for both new objects and for those being modified.

Sanitizing a Phone Number

You might recall that, at the conclusion of last week's article, I challenged you to use validates_format_of() to validate a phone number. The difficulty of doing so lies within accounting for a wide variety of phone number formats, (NNN) NNN-NNNN, NNN-NNN-NNNN, and NNN.NNN.NNNN among them. Sure, you could simply enforce a particular format and display an error message if the user doesn't follow it, or divide the phone number input field into three separate input fields, but the most user-friendly approach would be to allow the user to use whatever format he pleases and then strip out any non-numerical characters. You can strip out these characters using the gsub() method. To try it out, fire up the Rails console and execute the following command:

>> "(614) 999-9999".gsub(/[^0-9]/, "")
=> "6149999999"

In this case, the gsub() method replaces any character not found in the range 0-9 with nothing (essentially removing them), and returning what remains. You can use this approach to deal with any of the aforementioned phone number formats. Logically, you'll still want to verify that the user provided a total of ten digits, so you can strip these characters before validation by using before_validation_on_create():

class Question < ActiveRecord::Base

   def before_validation_on_create
      self.phone = phone.gsub(/[^0-9]/, "")
   end

end

Of course, because this happens before validation you're free to institute the typical validations required of a phone number, such as whether it exists in the first place (presuming you've decided to make this a mandatory part of user input), and whether it consists of 10 digits:

class Question < ActiveRecord::Base

   def before_validation_on_create
      self.phone = phone.gsub(/[^0-9]/, "")
   end

   validates_presence_of :phone, "can't be blank!"
   validates_length_of :phone, :is=>10,
      "must consist of 10 digits!"

end

Assigning Support Request Priority Based on Email Address

Suppose you added another column, named priority, to the Question model. This column will be used internally to identify requests coming from a particularly lucrative client as particularly urgent. By parsing the email address, you can identify the domain name, and flag it as being of high priority. Because you would only want to do so when the email address has been deemed valid, you should use the after_validation_on_create() method. The following code should do the trick:

class Question < ActiveRecord::Base

   def after_validation_on_create

      address = self.email.split('@')

      if address[1] == "example.com"
         self.priority = 1
      else
         self.priority = 0
      end
   end

end

In a real-world situation, chances are high you'll actually have several high-profile clients, and therefore might identify clients as of a certain priority within the Client model. In such a case, you could modify the above code to look to the appropriate client table and determine whether the support request is coming from a member of one of the high-profile clients. Nonetheless, the above example should give you ample indication of how to implement such a task.

Removing Attribute Names From Error Messages

One of the oddities regarding how Rails displays error messages is the prefixing of the corresponding Model attribute name to the front of the message. For example, you'll receive this message if you leave the name form field blank when attempting to submit a support request:

Name can't be blank!

That works, but what if you wanted to use a somewhat more grammatically correct message, such as "Your first and last name are required."? Based on this default prefixing, the error message would look like this:

Name Your first and last name are required.

A reader posed this question to me a few days ago, and after a bit of research I've located a great solution. To remove these prefixes, just install the Custom Error Message plugin. Then, all you have to do is prefix your custom message with ^, like so:

validates_presence_of :name,
   :message=>"^Your first and last name are required."

Conclusion

Rails' Active Record implementation offers an unbelievable array of validation features that can go a very long way towards ensuring user input is provided exactly as you intend it to be. Hopefully, this two-article series helps you understand these sometimes cryptic features.

About the Author

W. Jason Gilmore is co-founder of IT Enlightenment. He's the author of several books, including the best-selling Beginning PHP and MySQL 5: Novice to Professional, Second Edition (Apress, 2006. 913pp.). Jason loves receiving email, so don't hesitate to write him at wjATwjgilmore.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date