- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols
Chapter 19
Active Record Part III:
Object Life Cycle
So far we’ve looked at how to connect to Active Record, access data and attributes, and link together tables. This chapter rounds off our description of Active Record. It looks at the life cycle of Active Record objects: the validations and hooks that you can define affect how they are processed.
19.1Validation
Active Record can validate the contents of a model object. This validation can be performed automatically when an object is saved. You can also programmatically request validation of the current state of a model. If validation fails when you’re saving an object, the object will not be written to the database; it will be left in memory in its invalid state. This allows you (for example) to pass the object back to a form so the user can correct the bad data.
Active Record distinguishes between models that correspond to an existing row in the database and those that don’t. The latter are called new records (the new_record? method will return true for them). When you call the save method, Active Record will perform an SQL insert operation for new records and an update for existing ones.
This distinction is reflected in Active Record’s validation workflow—you can specify validations that are performed on all save operations and other validations that are performed only on creates or updates.
At the lowest level you specify validations by implementing one or more of the methods validate, validate_on_create, and validate_on_update. The validate method is invoked on every save operation. One of the other two is invoked
VALIDATION 364
depending on whether the record is new or whether it was previously read from the database.
You can also run validation at any time without saving the model object to the database by calling the valid? method. This invokes the same two validation methods that would be invoked if save had been called.
For example, the following code ensures that the user name column is always set to something valid and that the name is unique for new User objects. (We’ll see later how these types of constraints can be specified more simply.)
class User < ActiveRecord::Base
def validate
unless name && name =~ /^\w+$/ errors.add(:name, "is missing or invalid")
end end
def validate_on_create
if User.find_by_name(name)
errors.add(:name, "is already being used") end
end end
When a validate method finds a problem, it adds a message to the list of errors for this model object using errors.add. The first parameter is the name of the offending attribute, and the second is an error message. If you need to add an error message that applies to the model object as a whole, use the add_to_base method instead. (Note that this code uses the support method blank?, which returns true if its receiver is nil or an empty string.)
def validate
if name.blank? && email.blank?
errors.add_to_base("You must specify a name or an email address") end
end
As we’ll see on page 493, Rails views can use this list of errors when displaying forms to end users—the fields that have errors will be automatically highlighted, and it’s easy to add a pretty box with an error list to the top of the form.
You can get the errors for a particular attribute using errors.on(:name) (aliased to errors[:name]), and you can clear the full list of errors using errors.clear. If you look at the API documentation for ActiveRecord::Errors, you’ll find a number of other methods. Most of these have been superseded by higher-level validation helper methods.
Report erratum
VALIDATION 365
Validation Helpers
Some validations are common: this attribute must not be empty, that other attribute must be between 18 and 65, and so on. Active Record has a set of standard helper methods that will add these validations to your model. Each is a class-level method, and all have names that start validates_. Each method takes a list of attribute names optionally followed by a hash of configuration options for the validation.
For example, we could have written the previous validation as
class User < ActiveRecord::Base |
|
validates_format_of :name, |
|
:with |
=> /^\w+$/, |
:message => "is missing or invalid" |
|
validates_uniqueness_of :name, |
|
:on |
=> :create, |
:message => "is already being used"
end
The majority of the validates_ methods accept :on and :message options. The :on option determines when the validation is applied and takes one of the values :save (the default), :create, or :update. The :message parameter can be used to override the generated error message.
When validation fails, the helpers add an error object to the Active Record model object. This will be associated with the field being validated. After validation, you can access the list of errors by looking at the errors attribute of the model object. When Active Record is used as part of a Rails application, this checking is often done in two steps.
1.The controller attempts to save an Active Record object, but the save fails because of validation problems (returning false). The controller redisplays the form containing the bad data.
2.The view template uses the error_messages_for method to display the error list for the model object, and the user has the opportunity to fix the fields.
We cover the interactions of forms and models in Section 22.5, Error Handling and Model Objects, on page 493.
The pages that follow contain a list of the validation helpers you can use in model objects.
Report erratum
VALIDATION 366
validates_acceptance_of
Validates that a checkbox has been checked.
validates_acceptance_of attr... [ options... ]
Many forms have a checkbox that users must select in order to accept some terms or conditions. This validation simply verifies that this box has been checked by validating that the value of the attribute is the string 1 (or the value of the :accept parameter). The attribute itself doesn’t have to be stored in the database (although there’s nothing to stop you storing it if you want to record the confirmation explicitly).
class Order < ActiveRecord::Base validates_acceptance_of :terms,
|
|
:message => "Please accept the terms to proceed" |
end |
|
|
Options: |
|
|
:accept |
value |
The value that signifies acceptance (defaults to 1) |
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:message |
text |
Default is “must be accepted” |
:on |
|
:save, :create, or :update |
validates_associated
Performs validation on associated objects.
validates_associated name... [ options... ]
Performs validation on the given attributes, which are assumed to be Active Record models. For each attribute where the associated validation fails, a single message will be added to the errors for that attribute (that is, the individual detailed reasons for failure will not appear in this model’s errors).
Be careful not to include a validates_associated call in models that refer to each other: the first will try to validate the second, which in turn will validate the first, and so on, until you run out of stack.
class Order < ActiveRecord::Base has_many :line_items belongs_to :user
validates_associated :line_items,
:message => "are messed up" validates_associated :user
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:message |
text |
Default is “is invalid” |
:on |
|
:save, :create, or :update |
Report erratum
VALIDATION 367
validates_confirmation_of
Validates that a field and its doppelgänger have the same content.
validates_confirmation_of attr... [ options... ]
Many forms require a user to enter some piece of information twice, the second copy acting as a confirmation that the first was not mistyped. If you use the naming convention that the second field has the name of the attribute with _confirmation appended, you can use validates_confirmation_of to check that the two fields have the same value. The second field need not be stored in the database.
For example, a view might contain
<%= password_field "user", "password" %><br />
<%= password_field "user", "password_confirmation" %><br />
Within the User model, you can validate that the two passwords are the same using
class User < ActiveRecord::Base validates_confirmation_of :password
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:message |
text |
Default is “doesn’t match confirmation” |
:on |
|
:save, :create, or :update |
validates_each
Validates one or more attributes using a block.
validates_each attr... [ options... ] { |model, attr, value| ... }
Invokes the block for each attribute (skipping those that are nil if :allow_nil is true). Passes in the model being validated, the name of the attribute, and the attribute’s value. As the following example shows, the block should add to the model’s error list if a validation fails.
class User < ActiveRecord::Base
validates_each :name, :email do |model, attr, value| if value =~ /groucho|harpo|chico/i
model.errors.add(attr, "You can't be serious, #{value}")
end end
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If :allow_nil is true, attributes with values of nil will not be passed into the |
|
|
block. By default they will. |
:if |
code |
See discussion on page 372. |
:on |
|
:save, :create, or :update. |
Report erratum
VALIDATION 368
validates_exclusion_of
Validates that attributes are not in a set of values.
validates_exclusion_of attr..., :in => enum [ options... ]
Validates that none of the attributes occurs in enum (any object that supports the include? predicate).
class User < ActiveRecord::Base validates_exclusion_of :genre,
:in => %w{ polka twostep foxtrot }, :message => "no wild music allowed"
validates_exclusion_of :age,
|
|
:in => 13..19, |
|
|
:message => "cannot be a teenager" |
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:in (or :within) |
enumerable |
An enumerable object |
:message |
text |
Default is “is not included in the list.” |
:on |
|
:save, :create, or :update |
validates_format_of
Validates attributes against a pattern.
validates_format_of attr..., :with => regexp [ options... ]
Validates each of the attributes by matching its value against regexp.
class User < ActiveRecord::Base
validates_format_of :length, :with => /^\d+(in|cm)/
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:message |
text |
Default is “is invalid” |
:on |
|
:save, :create, or :update |
:with |
|
The regular expression used to validate the attributes |
Report erratum
VALIDATION 369
validates_inclusion_of
Validates that attributes belong to a set of values.
validates_inclusion_of attr..., :in => enum [ options... ]
Validates that the value of each of the attributes occurs in enum (any object that supports the include? predicate).
class User < ActiveRecord::Base validates_inclusion_of :gender,
:in => %w{ male female },
:message => "should be 'male' or 'female'" validates_inclusion_of :age,
:in => 0..130,
|
|
:message => "should be between 0 and 130" |
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:in (or :within) |
enumerable |
An enumerable object |
:message |
text |
Default is “is not included in the list” |
:on |
|
:save, :create, or :update |
validates_length_of
Validates the length of attribute values.
validates_length_of attr..., [ options... ]
Validates that the length of the value of each of the attributes meets some constraint: at least a given length, at most a given length, between two lengths, or exactly a given length. Rather than having a single :message option, this validator allows separate messages for different validation failures, although :message may still be used. In all options, the lengths may not be negative.
class User < ActiveRecord::Base validates_length_of :name, :maximum => 50 validates_length_of :password, :in => 6..20 validates_length_of :address, :minimum => 10,
:message => "seems too short"
end
continued over...
Report erratum
VALIDATION 370
Options (for validates_length_of):
:allow_nil |
boolean |
If true, nil attributes are considered valid. |
:if |
code |
See discussion on page 372. |
:in (or :within) |
range |
The length of value must be in range. |
:is |
integer |
Value must be integer characters long. |
:minimum |
integer |
Value may not be less than the integer characters long. |
:maximum |
integer |
Value may not be greater than integer characters long. |
:message |
text |
The default message depends on the test being performed. The mes- |
|
|
sage may contain a single %d sequence, which will be replaced by the |
|
|
maximum, minimum, or exact length required. |
:on |
|
:save, :create, or :update. |
:too_long |
text |
A synonym for :message when :maximum is being used. |
:too_short |
text |
A synonym for :message when :minimum is being used. |
:wrong_length |
text |
A synonym for :message when :is is being used. |
validates_numericality_of
Validates that attributes are valid numbers.
validates_numericality_of attr... [ options... ]
Validates that each of the attributes is a valid number. With the :only_integer option, the attributes must consist of an optional sign followed by one or more digits. Without the option (or if the option is not true), any floating-point format accepted by the Ruby Float method is allowed.
class User < ActiveRecord::Base validates_numericality_of :height_in_meters validates_numericality_of :age, :only_integer => true
end |
|
|
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on page 372 |
:message |
text |
Default is “is not a number” |
:on |
|
:save, :create, or :update |
:only_integer |
|
If true, the attributes must be strings that contain an optional sign |
|
|
followed only by digits |
validates_presence_of
Validates that attributes are not empty.
validates_presence_of attr... [ options... ]
Validates that each of the attributes is neither nil nor empty.
class User < ActiveRecord::Base validates_presence_of :name, :address
end
Report erratum
VALIDATION 371
Options: |
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid |
:if |
code |
See discussion on the following page |
:message |
text |
Default is “can’t be empty” |
:on |
|
:save, :create, or :update |
validates_size_of
Validates the length of an attribute.
validates_size_of attr..., [ options... ]
Alias for validates_length_of.
validates_uniqueness_of
Validates that attributes are unique.
validates_uniqueness_of attr... [ options... ]
For each attribute, validates that no other row in the database currently has the same value in that given column. When the model object comes from an existing database row, that row is ignored when performing the check. The optional :scope parameter can be used to filter the rows tested to those having the same value in the :scope column as the current record.
This code ensures that user names are unique across the database.
class User < ActiveRecord::Base validates_uniqueness_of :name
end
This code ensures that user names are unique within a group.
class User < ActiveRecord::Base validates_uniqueness_of :name, :scope => "group_id"
end
Except...despite its name, validates_uniqueness_of doesn’t really guarantee that column values will be unique. All it can do is verify that no column has the same value as that in the record being validated at the time the validation is performed. It’s possible for two records to be created at the same time, each with the same value for a column that should be unique, and for both records to pass validation. The most reliable way to enforce uniqueness is with a database-level constraint.
continued over...
Report erratum
|
|
VALIDATION |
372 |
Options: |
|
|
|
:allow_nil |
boolean |
If true, nil attributes are considered valid. |
|
:case_sensitive |
boolean |
If true (the default), an attempt is made to force the test to be case |
|
|
|
sensitive; otherwise case is ignored. This option works onlyif your |
|
|
|
database is configured to support case-sensitive comparisons in con- |
|
|
|
ditions. |
|
:if |
code |
See discussion on the current page. |
|
:message |
text |
Default is “has already been taken.” |
|
:on |
|
:save, :create, or :update. |
|
:scope |
attr |
Limits the check to rows having the same value in the column as the |
|
|
|
row being checked. |
|
Conditional Validation
All validation declarations take an optional :if parameter that identifies some code to be run. The parameter may be
•A symbol, in which case the corresponding method is called, passing it the current Active Record object
•A string, which is evaluated (by calling eval)
•A Proc object, which will be called, passing it the current Active Record object
If the code returns false, this particular validation is skipped.
The :if option is commonly used with a Ruby proc, because these allow you to write code whose execution is deferred until the validation is performed. For example, you might want to check that a password was specified and that it matches its confirmation (the duplication password you ask users to enter). However, you don’t want to perform the confirmation check if the first validation would fail. You achieve this by running the confirmation check only if the password isn’t blank.
validates_presence_of :password
validates_confirmation_of :password, :message => "must match confirm password", :if => Proc.new { |u| !u.password.blank? }
Validation Error Messages
The default error messages returned by validation are built into Active Record. You can, however, change them programmatically. The messages are stored in a hash, keyed on a symbol. It can be accessed as
ActiveRecord::Errors.default_error_messages
The values at the time of writing are
:accepted |
=> |
"must be accepted" |
:blank |
=> |
"can't be blank" |
Report erratum