Advanced Active Record Validations with Rails
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