Data integrity is an underrated part of proper application architecture. Many of the bugs in production systems are triggered by missing or malformed user data. If a user can possibly screw it up or screw with it, they will. Validations in the model can help!
Before we begin, let's talk about syntax. There are two primary syntaxes for writing validations in Rails 3:
validates_presence_of :price
validates :price, :presence => true
These have the exact same functionality. The first is considered "Rails 2 style" and the second a newer "Rails 3 style". The Rails 3 style shines when we add in a second validation on the same field:
# Rails 2 Style
validates_presence_of :price
validates_numericality_of :price
# Rails 3 Style
validates :price, :presence => true, :numericality => true
The newer syntax allows you to condense multiple validations into a single line of code.
The newer syntax is not good. Clean Ruby reads like English. To read the second set of examples aloud:
Rails 2 Style
validates presence of price, validates numericality of price
Rails 3
validates price presence true numericality true
The Rails 2 sentence isn't poetry, but you can understand what it means. The Rails 3 syntax sounds like computer talk. Ruby is about developers not computers, and for that reason I recommend you not use the new syntax.
I contacted Aaron Patterson on the Rails core team and he confirmed that there are no plans to deprecate the "older" syntax. I use it in my projects and tutorials.
There are many validations available to you, check out the full API here: http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html
Let's take a closer look at the most commonly used methods.
Declare that the field must have some value. Note that an empty string (""
) is not considered a value.
validates_presence_of :title
Also, you can declare the validation on multiple fields at once:
validates_presence_of :title, :price, :description
Check that the value in the field "looks like" a number. It might be a string, but does it look like a number? "123"
does, "3.45"
does, while "hello"
does not. Neither does "a928"
. I'll tend to apply this to every field that is a number in the database (ex: price
) and ones that look like a number but are stored as a string (ex: zipcode
).
validates_numericality_of :price
That basic usage will allow anything that's "number-like" including integers or floats.
We can add a few options to add criteria to our "numbers":
-
:only_integer
will only accept integersvalidates_numericality_of :price, :only_integer => true
-
Control the range of values with these options:
:greater_than
:greater_than_or_equal_to
:less_than
:less_than_or_equal_to
For example:
validates_numericality_of :price, :greater_than => 0 validates_numericality_of :price, :less_than => 1000 validates_numericality_of :price, :greater_than => 0, :less_than => 1000
Check the length of a string with validates_length_of
.
validates_length_of
obviously needs to know what the length should be. Here are a few examples of the common specifiers:
validates_length_of :zipcode, :is => 5
validates_length_of :title, :minimum => "10"
validates_length_of :title, :maximum => "1000"
validates_length_of :title, :in => (10..1000)
The validates_format_of
method is the Swiss Army knife of validations. It attempts to match the input against a regular expression, so anything you can write in a regex you can check with this validator.
I always like to share Jamie Zawinski's quote when talking about this topic: "Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' Now they have two problems." (http://en.wikiquote.org/wiki/Jamie_Zawinski)
But if you're comfortable and capable with regular expressions, have at it!
The canonical example is email address format validation:
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
Or reject based on a regex using the :without
option:
validates_format_of :comment, :without => /(<script>|<\/script>)/
Check that a value is in a given set with validates_inclusion_of
.
validates_inclusion_of :birth_year, :in => (1880..2011)
The :in
parameter will accept a Ruby range like this example or any other Enumerable
object (like an Array
).
Rare is the true need for a custom validation, but it can be done when none of the above will work.
validate :not_spammy
def not_spammy
if self.description.downcase.include?("enhancement")
errors.add(:base, "The description sounds spammy")
end
end
Call the validate
method with a symbol specifying the instance method to be called. When a record is saved, that method will be called. Rails will not respect the return value of that method, it determines pass/fail based on whether any error messages were added to the object.
Do any inspections/calculations/verifications you want in the called method and make the validation to fail by calling errors.add(:base, message)
. If no errors are added, the validation passes.
Having expressed validations in the model, let's see how to make use of them in the user interface.
As an example, assume I have this model:
class Product < ActiveRecord::Base
validates_presence_of :title
end
Then I can experiment in the console:
ruby-1.9.2-p290 :001 > p = Product.new
=> #<Product id: nil, title: nil, price: nil, description: nil, image_url: nil, created_at: nil, updated_at: nil, stock: 0>
ruby-1.9.2-p290 :002 > p.valid?
=> false
Calling the valid?
method on an instance will run the validations and return true
or false
. From there, we can dig into the errors:
ruby-1.9.2-p290 :003 > p.errors
=> {:title=>["can't be blank"]}
ruby-1.9.2-p290 :004 > p.errors.full_messages
=> ["Title can't be blank"]
The .errors
method returns an ActiveRecord::Errors
collection, which looks like a hash, with the attribute name as the key and the message(s) in an array. The convenience method .full_messages
on that object returns an array of nicely formatted sentence fragments.
While looking at validation failures, let's highlight the difference between .save
and .save!
. Both methods run your validations, of course, but the consequences of a failure are different.
For example:
ruby-1.9.2-p290 :001 > p = Product.new
=> #<Product id: nil, title: nil, price: nil, description: nil, image_url: nil, created_at: nil, updated_at: nil, stock: 0>
ruby-1.9.2-p290 :002 > p.save
=> false
ruby-1.9.2-p290 :003 > p.save!
ActiveRecord::RecordInvalid: Validation failed: Title can't be blank
A call to save
will return true
if the save succeeds and false
if it fails. In your application code, you should react to this return value to determine next steps. For example:
def create
@product = Product.new(params[:product])
if @product.save
redirect_to @product, :notice => "Successfully created product."
else
render :action => 'new'
end
end
The if
/else
switches based on the success/failure of the save
.
Alternatively, when we use save!
...
ruby-1.9.2-p290 :001 > p = Product.new
=> #<Product id: nil, title: nil, price: nil, description: nil, image_url: nil, created_at: nil, updated_at: nil, stock: 0>
ruby-1.9.2-p290 :002 > p.save!
ActiveRecord::RecordInvalid: Validation failed: Title can't be blank
When .save!
succeeds it will also return true
, but when it fails it will raise an exception. Well architected Ruby treats exceptions as extremely abnormal cases. For that reason, saving a model should only raise an exception when something very strange has happened, like the database has crashed. Users entering junky input is not unexpected, so we shouldn't typically raise exceptions on a validation.
When should you use save!
? When you expect the validations to always pass. For instance, if your application is creating objects with no user input, it'd be very strange to have invalid data. Then use save!
and skip the redirect. In such a scenario, redirecting someplace probably isn't going to help, so your application should freak out.
Typically we react to .save
in the controller and re-render the form if it returned false
. Then in the UI we can display the errors for the user to correct.
In the olden days, this was super easy. All you had to write in the helper was this, assuming we're talking about a @product
instance:
<%= error_messages_for @product %>
You'd get a nice box saying that there were errors and a bulleted list of the error messages. Assuming your form is using the form_for
helper, the fields with the validation errors will automatically be wrapped with a <div class='field_with_error></div>
. Awesome!
But then your designer gets a hold of things and they flip out. "What is this error box crap? I want an ordered list! And sparkles!" The error_messages_for
helper didn't support any customization, so most production apps would end up rewriting it.
With Rails 3 the error_messages_for
helper was removed. The automatic wrapping of <div class='field_with_error></div>
remains.
But now we can write our own error_messages_for
, imitate Rails 2, and leave the door open for easy customization at the design stage. Here's an implementation mercilessly stolen from Ryan Bates of RailsCasts:
def error_messages_for(*objects)
options = objects.extract_options!
options[:header_message] ||= I18n.t(:"activerecord.errors.header", :default => "Invalid Fields")
options[:message] ||= I18n.t(:"activerecord.errors.message", :default => "Correct the following errors and try again.")
messages = objects.compact.map { |o| o.errors.full_messages }.flatten
unless messages.empty?
content_tag(:div, :class => "error_messages") do
list_items = messages.map { |msg| content_tag(:li, msg) }
content_tag(:h2, options[:header_message]) + content_tag(:p, options[:message]) + content_tag(:ul, list_items.join.html_safe)
end
end
end
It's CSS-compatible with the original Rails 2 implementation and respects i18n message definitions. Speaking of i18n...
All the validations support a :message
parameter in the model where you can specify a custom message. But this is the wrong way to do it, and I hope that option is soon deprecated.
Instead, the messages should be specified in our translation file. These files live in config/locales/
and have names corresponding to the language code, like en.yml
for English or es.yml
for Spanish.
In that translation file you can override the default messages either globally for all uses of a validation or on a per-model basis. Check out the Rails source code for lots of details on implementation: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml
Here's a simple example based on validates_presence_of :title
for a Product
:
en:
activerecord:
errors:
models:
product:
attributes:
title:
blank: "The title can't be blank, duh!"
Note that when using this approach, you'll want to iterate through @product.errors
and pull out the messages from the array rather than using the .full_messages
helper.
Web applications generally do a terrible job of coaching their users. You fill out a big form, click submit, then it'll have some snarky message at the top of the form like "Form Had Errors".
To really treat users with respect, we should run validations and give feedback as soon as possible. That means handling it on the client-side in JavaScript.
Should we re-implement all our model validations in JavaScript? No!
The Client-Side Validations gem (https://github.com/bcardarella/client_side_validations) will take care of everything for you. It will read your model validations, wrap them up into a JSON package, and send it to the client along with the form. Combined with a simple JavaScript engine to process that JSON, you get a great user experience with very little work.
Check out the project page and readme for details on setup and usage.
For truly bullet-proof data integrity you'll need to implement validations at the database level, too.
-
The Foreigner gem adds simple migration instructions for adding foreign key constraints to MySQL, PostgreSQL, and SQLite: https://github.com/matthuhiggins/foreigner
-
validates_uniqueness_of
could, in theory, run into issues if there are two concurrent requests creating the same data. To protect against that, you can create a database index on the field and specify that it must be unique:# in your migration... t.index(:title, :unique => true)
Then the database would reject a second submission with an existing title if it got past the Rails model validation
[TODO: Add exercises]
- Rails API for
validates_X_of
methods: http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html - Rails API for
validates
syntax: http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates - Tricks and techniques for Ruby exceptions: http://exceptionalruby.com/
- Client-Side Validations Gem: https://github.com/bcardarella/client_side_validations
- RailsCast on Client-Side Validations: http://railscasts.com/episodes/263-client-side-validations
- ActiveRecord i18n Validation Messages: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml
- Rails Guide on i18n for Models: http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
- Rails migration methods including
index
: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
In your
validates_format_of
section, I believe you'll need to escape that forward slash in</script>
in this line: