Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

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