While we’d like visitors to enter their names, it’s usually best not
to be too picky about names, because they come in so many varieties. On
the other hand, the :secret
field is
ripe with opportunities for demanding expectations. Along the way, this
example will demonstrate how you can use multiple validations on the
same field in sequence.
The :secret
field needs to be
present. Sometimes, though, it’s worth telling a user why a particular
mistake matters rather than just insisting, “field_name can’t be blank.” Rails makes
that easy to do by letting you specify a :message
to go with your validation. If the validation fails, the
user sees the :message
. The code
below adds a message to the test for :secret
’s presence:
# secret is also mandatory, but let's alter the default Rails message to be
# more friendly
validates_presence_of :secret,
:message => "must be provided so we can recognize you in the future"
If the user leaves the :secret
field blank, they’ll see a custom
error message as shown in Figure 7-4.
Even if the user provides a :secret
, though, not all :secret
s are created equal. Another set of
validations will test the actual content of :secret
, as shown here:
# ensure secret has enough letters, but not too many validates_length_of :secret, :in => 6..24 # ensure secret contains at least one number validates_format_of :secret, :with => /[0-9]/, :message => "must contain at least one number" # ensure secret contains at least one upper case validates_format_of :secret, :with => /[A-Z]/, :message => "must contain at least one upper case character" # ensure secret contains at least one lower case validates_format_of :secret, :with => /[a-z]/, :message => "must contain at least one lower case character"
The first of these validations tests the length of :secret
,
making sure that it lies between a 6-character minimum and a
24-character maximum:
validates_length_of :secret, :in => 6..24
Rails is smart enough that if a user enters a password that’s too short, it will report back that:
Secret is too short (minimum is 6 characters)
And it will do the same for the maximum. There probably isn’t
any need to customize the :message
.
However, the next three validations use the power of regular
expressions. Regular expressions, or regexes,
are compact but powerful patterns that Rails will test against the
value of :secret
. If the testing of
:secret
against the regular
expression specified in :with
returns true
, then the validation
passes and all is well. If it flunks the test, then the specified
message will go out to the user.
All of these tests will be performed in sequence, and the user
will see an error message reflecting all the tests that flunked. For
example, a blank :secret
will yield
the full set shown in Figure 7-5.
Regular expressions are a complex subject you can study to nearly infinite depth. Appendix C offers “An Incredibly Brief Guide to Regular Expressions,” which can get you started. Jeffrey Friedl’s Mastering Regular Expressions (O’Reilly, 2006) is pretty much the classic overview of the field, but Tony Stubblebine’s Regular Expression Pocket Reference (O’Reilly, 2007) is a concise guide to the capabilities and syntax in different environments.
The form created in the previous chapter only supported four
values for the :country
field.
Limiting the values in the form, however, isn’t very limiting. Other
values could come in from other forms or, more simply, from an XML
request using the REST interface. If we want to limit the
values it can have, the data model is the place to do that:
# the country field is a controlled vocabulary: we must check that # its value is within our allowed options validates_inclusion_of :country, :in => ['Canada', 'Mexico', 'UK', 'USA'], :message => "must be one of Canada, Mexico, UK or USA"
The validates_inclusion_of
method requires an :in
parameter that lists the possible
choices as an array, and in this case :message
specifies what the user will see if
it fails. There’s also a validates_exclusion_of
method that’s very similar, but flunks if the value
provided matches one of the specified values.
Regular expressions are useful for ensuring that :secret
contained certain patterns, but
sometimes you want make sure that a field actually matches a pattern.
The :email
field is a good
candidate for this, even though the simple regular expressions used to
check email addresses are hard to read if you haven’t spent a whole
lot of time with regular expressions:
# email should read like an email address; this check isn't exhaustive, # but it's a good start validates_format_of :email, :with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})/i, :message => "doesn't look like a proper email address"
The validates_format_of
method takes a field to check and a regular expression
for the :with
parameter. You’ll want to provide a :message
parameter, since Rails isn’t going
to know how to turn the regular expression into meaningful
explanations for ordinary web application users.
Validation isn’t always about the specific content of a field coming in. Sometimes it’s about how incoming data compares to existing data. The simplest and probably most common comparison is that for uniqueness. You don’t want multiple users to have the same username, or you don’t want multiple objects to have the same supposedly unique identifier, and so on.
You could write some code that checks all of the entries in your existing database to make sure that the new entry is unique, but Rails is happy to do that for you:
# how do we recognize the same person coming back? by their email address # so we want to ensure the same person only signs in once validates_uniqueness_of :email, :case_sensitive => false, :message => "has already been entered, you can't sign in twice"
The :case_sensitive
property lets you specify whether textual values should
be compared so that differences in case matter. The default value is
true
, but as email addresses are
not case-sensitive, false
is a
better option here. The :message
is
useful for explaining just what happened.
By default, validates_uniqueness_of
checks :email
only against the other values in the
same database column. If you wanted to ensure that it was unique
against multiple columns, the :scope
property would let you do that. For
instance, to check :email
against
:email
plus :name
and :secret
, you could write:
validates_uniqueness_of :email, :case_sensitive => false, :scope => [:name, :secret] :message => "has already been entered, you can't sign in twice"
Using :scope
makes more sense in more complicated applications with
multiple unique identifiers.
While many fields accept any text the user wants to provide, applications
tend to prefer 4.1 to “four and one-tenth” for numeric fields. By
default, as Figure 7-2 showed, Rails
doesn’t check that only numeric data goes into numeric fields. When it
puts text data into the database, the type conversion will yield a
zero—probably not what’s appropriate most of the time. Of course,
though, Rails lets you check this easily, along with a lot of details
that you may need to support your particular use of numbers. The
:graduation_year
field, for example,
comes with a lot of constraints as well as some openness. That’s easy
to check using validates_numericality_of
:
# Graduation year must be numeric, and within sensible bounds. # However, the person may not have graduated, so we allow a # nil value too. Finally, it must be a whole number (integer). validates_numericality_of :graduation_year, :allow_nil => true, :greater_than => 1920, :less_than_or_equal_to => Time.now.year, :only_integer => true
The first parameter here actually relaxes constraints.
Specifying :allow_nil => true
allows the value to stay blank. Only nonblank values
will have their value checked.
:allow_nil
is available for
all of the validates
methods.
You’ll want to use it wherever you don’t mean to place demands on
users.
The next two parameters are a verbose way of saying >
and <=
. The validates_numericality_of
methods offers a
set of parameters for testing numbers:
equal_to
Tests that the value being validated is equal to the value provided in the parameter.
even
Tests that the value is an even number (dividing by 2 yields no remainder).
greater_than
Tests that the value being validated is greater than the value provided in the parameter.
greater_than_or_equal_to
Tests that the value being validated is greater than or equal to the value provided in the parameter.
less_than
Tests that the value being validated is less than the value provided in the parameter.
less_than_or_equal_to
Tests that the value being validated is less than or equal to the value provided in the parameter.
odd
Tests that the value is an odd number (dividing by 2 yields a remainder of one).
only_integer
Tests that the value being validated is an integer, with no fractional part.
The named parameters have values. For the methods that make
comparisons, the value is the argument against which the incoming
value will be compared. These can be simple values or method calls,
such as :less_than_or_equal_to =>
Time.now.year
. For the boolean tests (even
, odd
, only_integer
), the value specifies whether
or not the test counts for validation, and the default value for all
of them is false
.
The next two fields, :body
temperature
and :price
,
are also numbers, with relatively simple validations:
# Body temperature doesn't have to be a whole number, but we ought to # constrain possible values. We assume our users aren't in cryostasis. validates_numericality_of :body_temperature, :allow_nil => true, :greater_than_or_equal_to => 60, :less_than_or_equal_to => 130, :only_integer => false validates_numericality_of :price, :allow_nil => true, :only_integer => false