Managing Secrets

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.

Customizing the Message

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 :secrets 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"
A custom error message sent to the user

Figure 7-4. A custom error message sent to the user

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.

A multiply validated (and multiply flunked) secret

Figure 7-5. A multiply validated (and multiply flunked) secret

Note

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.

Limiting Choices

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.

Testing Format with Regular Expressions

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.

Seen It All Before

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.

Numbers Only

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.

Note

: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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset