Enforcing Data Integrity with Active Record Validations

Problem

Your application's users will make mistakes while entering information into forms: after all, they wouldn't be users if they didn't. Therefore, you want to validate form data without creating a bunch of boilerplate error-checking code. Since you're security conscious, you want to do validation on the server, and you want to prevent attacks like SQL injection.

Solution

Active Record provides a rich set of integrated error validation methods that make it easy to enforce valid data.

Let's set up a form to populate the following students table:

mysql> desc students;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | | PRI | NULL | auto_increment |
| student_number | varchar(80) | YES | | NULL | |
| first_name | varchar(80) | YES | | NULL | |
| last_name | varchar(80) | YES | | NULL | |
| class_level | varchar(10) | YES | | NULL | |
| email | varchar(200) | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

We want to validate that student_number is actually a number, that class_level is a valid class (e.g., Freshman, Sophomore, etc.), and that email is a valid address. The three method calls in the following Student class handle all three of these validations:

class Student < ActiveRecord::Base
 validates_numericality_of :student_number
 validates_inclusion_of :class_level, 
 :in => %w( Freshmen Sophomore Junior Senior),
 :message=>"must be: Freshmen, Sophomore, Junior, or Senior" 
 validates_format_of :email, :with =>
 /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i end

Now we need to display error messages to the user, should the user enter invalid data. At the top of students/new.rhtml, we place a call to error_messages_for and pass it the model we are validating (student in this case). To illustrate per field error display, the Class level field calls error_message_on. This method takes the model as well as the field as arguments.

<h1>New student</h1>
<% form_tag :action => 'create' do %>
 <style> .blue { color: blue; } </style>
 <%= error_messages_for 'student' %>
 <p><label for="student_student_number">Student number</label>;
 <%= text_field 'student', 'student_number' %></p>
 <p><label for="student_first_name">First name</label>;
 <%= text_field 'student', 'first_name' %></p>
 <p><label for="student_last_name">Last name</label>;
 <%= text_field 'student', 'last_name' %></p>
 <p><label for="student_class_level">Class level</label>;
 <%= error_message_on :student, :class_level, "Class level ", "", "blue" %>
 <%= text_field 'student', 'class_level' %></p>
 <p><label for="student_email">Email</label>;
 <%= text_field 'student', 'email' %></p>
 <%= submit_tag "Create" %>
<% end %>
<%= link_to 'Back', :action => 'list' %>

Discussion

shows what happens when a user enters a new student record incorrectly.

Figure 3-4. The Student create view with errors displayed

The solution uses three of the validation methods that are built into Active Record. If you don't find a validation method that meets your needs in the following list, you are free to create your own. Here's a list of Active Record validation methods:

  • validates_acceptance_of
  • validates_associated
  • validates_confirmation_of
  • validates_each
  • validates_exclusion_of
  • validates_format_of
  • validates_inclusion_of
  • validates_length_of
  • validates_numericality_of
  • validates_presence_of
  • validates_size_of
  • validates_uniqueness_of

displays error messages in the errorExplanation style defined in the scaffold.css. If this is close to how you'd like to display errors, you can make your own adjustments to default styles. If you need to completely customize the handling of error messages (to send an email message, for example), you can access the object.errors instance directly and create your own structured output.

Note that we didn't have to do anything specific to prevent SQL injection attacks. It's sufficient to know that the student_number is indeed numeric, that the Student class is one of the four allowed strings, and that the email address matches our regular expression. It's going to be pretty hard to sneak some SQL by this application.

See Also