http://www.developer.com/

Back to article

Avoiding Data Corruption with Rails' Active Record Validations


November 12, 2007

Ask any experienced Web developer what is the most important single piece of advice he could pass along to less-experienced colleagues, and chances are very good he'll tell you it's that user input should not be trusted under any circumstances. This is because chances are at one point a careless, or even malicious bit of unchecked user-provided data has thrown a major monkey wrench into an otherwise well-oiled web application.

Unexamined user input has the potential to create all sorts of problems for Web developers, ranging from minor customer service issues such as the inability to respond to a client who has mistakenly provided an invalid email address to major losses of data due to the passage of malevolent commands to the operating system by way of an attack method known as SQL injection.

But, writing code capable of successfully validating and filtering user input is a tedious, error-prone process. Thankfully, it's a task that developers around the world also face, making such code a prime candidate for abstraction and reuse. In fact, the Ruby on Rails development team considered the matter so crucial that they built many such validation routines into the Rails framework itself. Integrated into Rails' Active Record implementation, these validation methods allow you to easily ensure the validity of your data model.

To demonstrate the utility of Active Record's validation routines, presume you were adding a customer support form to a corporate web site. The intent of this form is to provide customers with an easy way to contact customer service and ask questions about recently purchased products and services. As a timely and informed response will be significant in terms of keeping the cusotmer happy, it's crucial to ensure the provided user data is gathered and saved without error. This data will be stored in a model named Question, which consists of five data fields, including the client's name, email address, phone number, choice of contact method (phone or email), and finally the support question.

Create the Question Model

To follow along with this tutorial, begin by creating a new Rails project named corporate, and then create a new model named Question:

%>rails corporate
<snip>
%>ruby script/generate model Question
<snip>

The migration looks like this:

class CreateQuestions < ActiveRecord::Migration
   def self.up
      create_table :questions do |t|
         t.column :name,  :string, :null=>false
         t.column :email, :string, :null=>false
         t.column :phone, :string, :null=>false
         t.column :contact_method, :string, :null=>false
         t.column :message, :text, :null=>false
      end
   end

   def self.down
      drop_table :questions
   end
end

Creating the Support Form

Next up, you'll create the web that which the customer will use to submit questions. To do so, first create a controller named Support, in addition to two methods, one named index that will offer the form to the user, and another named submit that will process the form.

%>ruby script/generate controller Support index submit

The support request form as found in the index.rhtml view as follows:


<% if flash[:notice] %>
   <div class="notice"><%= flash[:notice] %></div>
<% end %>

<h3>Contact Customer Support</h3>

<%= form_tag '/support/submit' -%>
<p>
   <label for="shop_name">Your Name:</label><br/>
   <%= text_field "question", "name", "size" => 25 %>
</p>  

<p>
   <label for="question_phone">Your Phone Number:</label><br/>
   <%= text_field "question", "phone", "size" => 25 %>
</p>

<p>
   <label for="question_email">Your E-mail Address:</label><br/>
   <%= text_field "question", "email", "size" => 25 %>
</p>

<p>
   <label for="method">Preferred Contact Method?</label></br />
   <%= select("question", "contact_method", {"phone" => "Phone",
                                             "email" => "E-mail"}) %>
</p>

<p><label for="question_email">Your Question:</label><br/>
   <%= text_area "question", "message", "rows" => 5, "cols" => 35 %>
</p>

<p>
   <%= submit_tag "Submit!" %>
</p>
<%= end_form_tag -%>

Rendered to the browser, the support form looks like this:

Figure 1: The customer support form

Finally, implementing the submit method in its simplest form can be done in just a few lines:

def submit
   @question = Question.new(params[:question])
   if @question.save
      flash[:notice] = "Your question has been successfully sent to
                        customer service.
                        Would you like to submit another question?"
      render :action => "index"
   end
end

Although the support form is now functional, with no controls in place for validating input, chances are your support team will soon be greeted with support requests replete with blank names and messages, invalid email addresses, and incorrect phone numbers. The result? Angry customers! However, you can use Active Record's validation features to avoid such problems altogether.

Active Record's Validation Methods

Rails' Active Record validation features go a long way towards automating some of the most commonplace validation tasks, such as checking for the presence of a data field, determining string length, ensuring uniqueness, and even validating a string according to rules as defined by a regular expression. Setting the desired validation rules is as simple as modifying the model, as you'll soon see.

Validating Presence

Logically, you want to make sure the user completes the name, phone number, email address, and message fields. To do so, use the validates_presence_of method. To make sure these fields are not blank, modify the Question model so it looks like this:

class Question < ActiveRecord::Base
   validates_presence_of :name,    :message=>"can't be blank!"
   validates_presence_of :email,   :message=>"can't be blank!"
   validates_presence_of :phone,   :message=>"can't be blank!"
   validates_presence_of :message, :message=>"can't be blank!"
end

Next, you'll modify the submit method to display the form anew should errors occur:

   def submit
      @question = Question.new(params[:question])
      if @question.save
         flash[:notice] = "Your question has been successfully sent
                           to customer service.
                           Would you like to submit another question?"
         render :action => "index"
      else
         render :action => "index"
      end
   end

Finally, to display the error messages should a field not be completed, add the following line to the top of the form view (/support/index.rhtml):

<%= error_messages_for("question") %>

Go ahead and submit the form, but this time not completing any of the fields, and you'll be greeted with a response as shown in the following screenshot, followed by presentation of the form anew:

Figure 2: The result generated by the form.

By following a similar process, you can take advantage of Rails' other validation methods to impose further constraints upon your user input! I'll introduce a few others below.

Validating Input Length

You'll often want to validate the length of user input; for example, to ensure a password contains at least five characters. This is easily done with the validates_length_of method. For example, suppose you wanted to place a maximum limit of 500 characters for the question:

validates_length_of :message, :maximum=>500,
   :message=>"must be less than 500 characters!"

You can also place minimum limits, and even specify a range; for example, to force the user to type a message consisting of greater than 10 characters:

validates_length_of :message, :minimum=>500,
   :message=>"should be greater than 10 characters!"

Or, to momentarily deviate from the theme, suppose you wanted a registering user to create a password consisting of between 5 and 12 characters:

validates_length_of :pswd, :in=>5..12,
   :message=>"should be between 5 and 12 characters!"

Validating Format

The third of many validation methods at your disposal that I'd like to introduce is the validates_format_of method, which allows you to specify a free-form regular expression pattern that the input must successful match against for the rule to pass. This method is particularly useful for validating complex strings such as an email address:

validates_format_of :email,
   :with => /^\S+\@(\[?)[A-Za-z0-9\-\.]+\.([A-Za-z]{2,4}|
            [0-9]{1,4})(\]?)$/ix,
   :message=>"is not a valid e-mail address!"

Adding this rule will allow email addresses such as jason@example.com, jason@example.info, and jason@example.net, but disallow incorrect addresses such as jason@example, jason, and jason@.

As an exercise left to the reader, how might you go about validating the support form's phone number using validates_format_of? Remember, you'll need to take a number of variations into account; for instance, (123) 456-7890, (123) 4567890, and 123-233-2920.

Conclusion

This introductory tutorial on Rails' Active Record features can go a long way towards helping eliminate data corruption possibilities. Stay tuned for further articles exploring similar topics!

About the Author

W. Jason Gilmore is Apress' open source editor, and 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