Active Record Validations and Callbacks 活动记录验证和回调
Active Record Validations and Callbacks 活动记录验证和回调
This guide teaches you how to hook勾子into the life cycle of your Active Record objects.这个教程指导你怎样挂接到你的Active Record objects的生存周期。You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.你将会学习到在将数据对象存入数据库之前怎样验证它们的状态,以及在对象生存周期的一些点上怎样执行定制操作。
After reading this guide and trying out the presented concepts, we hope that you’ll be able to:在阅读了这个教程以及尝试介绍的概念,我们希望你能够:
- Understand the life cycle of Active Record objects 理解 Active Record对象的生存周期
- Use the built-in Active Record validation helpers 使用内建的 Active Record验证helpers
- Create your own custom validation methods 创建属于你的定制验证方法
- Work with the error messages generated by the validation process 在验证过程中使用错误消息创建器工作
- Create callback methods that respond to events in the object life cycle 新建一个回调方法响应对象生存周期的事件
- Create special classes that encapsulate封装common behavior for your callbacks 创建特殊的类来封装你回调的通常习惯(方法)
- Create Observers观察员that respond to life cycle events outside of the original class 创建 Observers来响应原类以外的生存周期事件
1 The Object Life Cycle
During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object life cycle so that you can control your application and its data.一个Rails应用程序的正常操作期间,对象可能被新建,更新,和销毁。Active Record提供挂载到这个对象生存周期,以便你可以控制你的应用程序和数据。
Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger触发logic before or after an alteration改造变动of an object’s state.验证允许你确保只有验证数据被存储到你的数据库。回调和观察员(监视器)允许你在变动一个对象的状态之前或之后触发逻辑。
2 Validations Overview验证概述
Before you dive into the detail of validations in Rails, you should understand a bit about how validations fit into the big picture.在你深入Rails 验证的详细说明之前,你应该明白一点就是关于怎样让验证适合于(Rails)这幅大画卷。
2.1 Why Use Validations?
Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address.验证用于确保只有通过验证的数据被保存入你的数据库。例如,在你的应用程序中确认每个用户提供了一个有效的Email地址和邮寄地址是非常重要的。
There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations.在数据被存储到你的数据库中之前这里有几种方法来验证它,包括本地数据库约束,客户端验证,控制层级别的验证和模型层级别的验证。
Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult. However, if your database is used by other applications, it may be a good idea to use some constraints at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise.
- Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user’s browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. 客户端验证是很有用的,但是一般情况下单独使用是靠不住的。如果他们使用JavaScript来实施(验证),它们可能被绕过如果用户的浏览器中JavaScript被关闭的话。然而,如果联合其他的技术,客户端验证是一种方便的方法来提供用户即使反馈(验证信息)如果他们使用你的的站点。
- Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Whenever possible, it’s a good idea to keepyourcontrollersskinny, as it will make your application a pleasure to work with in the long run. 控制层的验证的使用是诱人的,但是通常变得笨重和难以测试和维护。只要有可能,它是一个保持你的控制层苗条好主意,它也使得你的应用程序愉快的长时间工作。
- Model-level validations are the best way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well. 模板层的验证是最好的方式来确保只有有效的数据被保存进了你的数据库。它们与数据库无关,不能被终端用户绕过,并且方便测试和维护。Rails通过提供(基于)常规需要的内建的helpers,使得它们容易使用,并且同样允许你新建属于你的验证方法。
2.2 When Does Validation Happen?验证在什么时候发生?
There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the new method, that object does not belong to the database yet. Once you call save upon后that object it will be saved into the appropriate database table. Active Record uses the new_record? instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class:这里有两种Active Record对象:一些对应于你数据库中的一行以及一些不是。当你创建一个新鲜的对象,例如使用新的方法,这个对象还不属于数据库。一旦你调用save(函数)之后这个对象将会被适当的保存入数据表单。Active Record使用new_record?实例方法来决定一个对象是否已经被保存进了数据库。思考下面简单的Active Record类:
class
Person
<
ActiveRecord::Base
end
We can see how it works by looking at some rails console output:我们通过观察一些rails的控制台输可以明白它是怎么工作的:
$rails
console
>>
p
=
Person.new(:name
=>
“John
Doe”)
=>
#<Person
id:
nil,
name:
“John
Doe”,
created_at:
nil,
:updated_at:
nil>
>>
p.new_record?
=>
true
>>
p.save
=>
true
>>
p.new_record?
=>
false
Creating and saving a new record will send an SQL INSERT operation to the database. Updating an existing record will send an SQL UPDATE operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the INSERT or UPDATE operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.创建和保存一个新的记录将会发送一个SQL INSERT操作到数据库。更新一个存在的记录将会发送一个SQL UPDATE操作。验证通常在这些命令被发送到数据库之前运行。如果任何验证失败,这个对象将会标记为非法并且Active Record将不会执行INSERT或UPDATE操作。这有助于避免存储一个非法的数据到数据库。你可以在对象被创建,保存或更新的时候选择指定的验证执行。
There are many ways to change the state of an object in the database. Some methods will trigger触发validations, but some will not. This means that it’s possible to save an object in the database in an invalid state if you aren’t careful.这里有很多方法来改变对象在数据库中的状态。一些方法将会触发验证,但是一些却不会。这里的意思是如果你不仔细的话就可能以一种非法的状态保存一个对象到数据库。
The following methods trigger validations, and will save the object to the database only if the object is valid:下面的方法触发验证,并且如果对象是合法的话将会保存对象到数据库:
- create
- create!
- save
- save!
- update
- update_attributes
- update_attributes!
The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don’t: save and update_attributes return false, create and update just return the objects.有感叹号的形式(例如save!)在记录是非法的时候会唤起一个异常。没有感叹号形式的就不会:save和update_attributes返回false,create和update只是返回这个对象。
2.3 Skipping Validations忽略验证
The following methods skip validations, and will save the object to the database regardless无论of its validity. They should be used with caution.下面的方法会略过验证,并且将会保存对象到数据库而无论它的有效性。他们应该慎重使用。
- decrement! 递减
- decrement_counter
- increment!
- increment_counter
- toggle!
- touch 切换
- update_all
- update_attribute
- update_column
- update_counters
Note that save also has the ability to skip validations if passed :validate => false as argument. This technique should be used with caution.注意save也可以略过演奏如果通过使用:validate => false作为参数。这个技术也应该慎重使用。
- save(:validate => false)
2.4 valid? and invalid?
- To verify whether or not an object is valid, Rails uses the valid? method. You can also use this method on your own. valid? triggers your validations and returns true if no errors were added to the object, and false otherwise.验证一个对象是否有效,Rails使用valid?方法。你也可以使用属于你的方法。valid?触发你的验证并且返回True如果没有错误被添加到对象,否则就返回false。
class
Person
<
ActiveRecord::Base
validates
:name,
:presence
=>
true
end
Person.create(:name
=>
“John
Doe”).valid?
#
=>
true
Person.create(:name
=>
nil).valid?
#
=>
false
When Active Record is performing validations, any errors found can be accessed through the errors instance method. By definition an object is valid if this collection is empty after running validations.当Active Record在执行验证的时候,任何发现的错误都可以通过errors实例方法来访问。通过定义一个对象是有效的如果这个集合在运行验证过后是空的。
Note that an object instantiated with new will not report errors even if it’s technically invalid, because validations are not run when using new.注意当实例化一个新的对象的时候将不会报告错误即使假设它的验证技术(得出数据)是非有效的,因为在使用new的时候验证是没有运行的。
class
Person
<
ActiveRecord::Base
validates
:name,
:presence
=>
true
end
>>
p
=
Person.new
=>
#<Person
id:
nil,
name:
nil>
>>
p.errors
=>
{}
>>
p.valid?
=>
false
>>
p.errors
=>
{:name=>[“can’t
be
blank”]}
>>
p
=
Person.create
=>
#<Person
id:
nil,
name:
nil>
>>
p.errors
=>
{:name=>[“can’t
be
blank”]}
>>
p.save
=>
false
>>
p.save!
=>
ActiveRecord::RecordInvalid:
Validation
failed:
Name
can’t
be
blank
>>
Person.create!
=>
ActiveRecord::RecordInvalid:
Validation
failed:
Name
can’t
be
blank
invalid? is simply the inverse of valid?. invalid? triggers your validations and returns true if any errors were added to the object, and false otherwise. invalid?是valid?简单的逆。invalid?触发你的验证如果有任何错误添加到对象中则返回true,否则返回false。
2.5 errors[]
To verify whether or not a particular attribute of an object is valid, you can use errors[:attribute]. It returns an array of all the errors for :attribute. If there are no errors on the specified attribute, an empty array is returned.验证一个对象具体的属性是否有效,你可以使用errors[:attribute]。它返回有关:attribute所有错误的数组。如果指定的属性没有错误,将会返回一个空数组。
This method is only useful after validations have been run, because it only inspects the errors collection and does not trigger validations itself. It’s different from the ActiveRecord::Base#invalid? method explained above because it doesn’t verify the validity of the object as a whole. It only checks to see whether there are errors found on an individual attribute of the object.这个方法只有在验证被执行过后才有用,因为它仅仅检查错误集合却不会自己触发验证。它和ActiveRecord::Base#invalid?方法上面解释的不同因为它不会整个验证对象的有效性。它仅仅检查这个对象的个别属性是否有错误被找到。
class
Person
<
ActiveRecord::Base
validates
:name,
:presence
=>
true
end
>>
Person.new.errors[:name].any?
#
=>
false
>>
Person.create.errors[:name].any?
#
=>
true
We’ll cover validation errors in greater depth in the WorkingwithValidationErrors section. For now, let’s turn to the built-in validation helpers that Rails provides by default.我们将会非常深入的涵盖验证错误在WorkingwithValidationErrors。现在,让我们转入Rails默认提供的内建的验证helpers。
3 Validation Helpers
Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object’s errors collection, and this message is associated with the field being validated.Active Record提供许多预定义的你可以插入你的类中直接使用的验证helpers。这些helpers提供常规验证规则。每次验证失败,一个错误消息会被添加到对象的errors集合中,并且这些消息和被验证的field相关。
Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes.每个helper接受一个任意数目的属性名字,因此使用一行代码你可以给一些属性添加相同类型验证。
All of them accept the :on and :message options, which define when the validation should be run and what message should be added to the errors collection if it fails, respectively个别的. The :on option takes one of the values :save (the default), :create or :update. There is a default error message for each one of the validation helpers. These messages are used when the :message option isn’t specified. Let’s take a look at each one of the available helpers.所有的(helpers)接受:on和:message选项,用来对个别的(helpers)定义什么时候运行验证以及如果验证失败什么消息被添加到errors集合。:on选项获取:save (默认的), :create或:update的值。这里每个验证helpers是默认错误消息。这些消息在:message选项没有被指定时使用。下面我们来看看每个可用的helpers。
3.1 acceptance接受承认
Validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application’s terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and this ‘acceptance’ does not need to be recorded anywhere in your database (if you don’t have a field for it, the helper will just create a virtual attribute).这个是很特殊对于web应用程序的验证并且这个‘acceptance’ 不需要在你的数据库中的任何地方记录(如果没有一个它的field,helper将会仅仅创建一个虚拟属性)。
class
Person
<
ActiveRecord::Base
validates
:terms_of_service,
:acceptance
=>
true
end
The default error message for this helper is “must be accepted”.
It can receive an :accept option, which determines the value that will be considered acceptance. It defaults to “1” and can be easily changed.它可以接收一个:accept选项,用来决定哪个值认为是接受。默认是‘1’并且可以很容易更改成其他的。
class
Person
<
ActiveRecord::Base
validates
:terms_of_service,
:acceptance
=>
{
:accept
=>
‘yes’
}
end
3.2 validates_associated
You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, valid? will be called upon each one of the associated objects.
class
Library
<
ActiveRecord::Base
has_many
:books
validates_associated
:books
end
This validation will work with all of the association types.
Don’tusevalidates_associatedonbothendsofyourassociations.Theywouldcalleachotherinaninfinite无限loop.
The default error message for validates_associated is “is invalid”. Note that each associated object will contain its own errors collection; errors do not bubble up冒泡to the calling model.
3.3 confirmation确认
You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with “_confirmation” appended.
你应该使用这个helper当你有两个内容完全相同的文本框需要回收。(验证输入)
class
Person
<
ActiveRecord::Base
validates
:email,
:confirmation
=>
true
end
In your view template you could use something like
<%=
text_field
:person,
:email
%>
<%=
text_field
:person,
:email_confirmation
%>
This check is performed only if email_confirmation is not nil. To require confirmation, make sure to add a presence check for the confirmation attribute (we’ll take a look at presence later on this guide):
class
Person
<
ActiveRecord::Base
validates
:email,
:confirmation
=>
true
validates
:email_confirmation,
:presence
=>
true
end
The default error message for this helper is “doesn’t match confirmation”.
3.4 exclusion排除
This helper validates that the attributes’ values are not included in a given set. In fact, this set can be any enumerable列举object.
class
Account
<
ActiveRecord::Base
validates
:subdomain,
:exclusion
=>
{
:in
=>
%w(www
us
ca
jp),
:message
=>
“Subdomain
%{value}
is
reserved.”
}
end
The exclusion helper has an option :in that receives the set of values that will not be accepted for the validated attributes. The :in option has an alias called :within that you can use for the same purpose目的, if you’d like to. This example uses the :message option to show how you can include the attribute’s value.
The default error message is “is reserved”.
3.5 format格式(匹配)
This helper validates the attributes’ values by testing whether they match a given regular expression正则表达式, which is specified using the :with option.
class
Product
<
ActiveRecord::Base
validates
:legacy_code,
:format
=>
{
:with
=>
/\A[a-zA-Z]+\z/,
:message
=>
“Only
letters
allowed”
}
end
The default error message is “is invalid”.
3.6 inclusion列入
This helper validates that the attributes’ values are included in a given set. In fact, this set can be any enumerable object.
class
Coffee
<
ActiveRecord::Base
validates
:size,
:inclusion
=>
{
:in
=>
%w(small
medium
large),
:message
=>
“%{value}
is
not
a
valid
size”
}
end
The inclusion helper has an option :in that receives the set of values that will be accepted. The :in option has an alias called :within that you can use for the same purpose, if you’d like to. The previous example uses the :message option to show how you can include the attribute’s value.
The default error message for this helper is “is not included in the list”.
3.7 length
This helper validates the length of the attributes’ values. It provides a variety of options, so you can specify length constraints in different ways:
class Person < ActiveRecord::Base
validates :name, :length => { :minimum => 2 }
validates :bio, :length => { :maximum => 500 }
validates :password, :length => { :in => 6..20 }
validates :registration_number, :length => { :is => 6 }
end
The possible length constraint约束options are:
- :minimum – The attribute cannot have less than the specified length.
- :maximum – The attribute cannot have more than the specified length.
- :in (or :within) – The attribute length must be included in a given interval. The value for this option must be a range.
- :is – The attribute length must be equal to the given value.
The default error messages depend on the type of length validation being performed. Youcanpersonalizethesemessagesusingthe:wrong_length,:too_long,and:too_shortoptionsand%{count}asaplaceholderforthenumbercorrespondingtothelengthconstraintbeingused.You can still use the :message option to specify an error message.
class
Person
<
ActiveRecord::Base
validates
:bio,
:length
=>
{
:maximum
=>
1000,
:too_long
=>
“%{count}
characters
is
the
maximum
allowed”
}
end
This helper counts characters by default, but you can split the value in a different way using the :tokenizer option:
class
Essay
<
ActiveRecord::Base
validates
:content,
:length
=>
{
:minimum
=>
300,
:maximum
=>
400,
:tokenizer
=>
lambda
{
|str|
str.scan(/\w+/)
},
:too_short
=>
“must
have
at
least
%{count}
words”,
:too_long
=>
“must
have
at
most
%{count}
words”
}
end
Note that the default error messages are plural多元的(e.g., “is too short (minimum is %{count} characters)”). For this reason, when :minimum is 1 you should provide a personalized个性化message or use validates_presence_of instead. When :in or :within have a lower limit of 1, you should either provide a personalized message or call presence prior前to length.(当长度比1还小就为空了应该使用validates_presence_of来验证)
The size helper is an alias for length.
3.8 numericality只能输入数字
This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set :only_integer to true.
If you set :only_integer to true, then it will use the
/\A[+–]?\d+\Z/
regular expression to validate the attribute’s value. Otherwise, it will try to convert the value to a number using Float.
Notethattheregularexpressionaboveallowsatrailingnewlinecharacter.请注意,上面的正则表达式允许一个尾随的换行符
class
Player
<
ActiveRecord::Base
validates
:points,
:numericality
=>
true
validates
:games_played,
:numericality
=>
{
:only_integer
=>
true
}
end
Besides :only_integer, this helper also accepts the following options to add constraints to acceptable values:
- :greater_than – Specifies the value must be greater than the supplied value. The default error message for this option is “must be greater than %{count}”. 大于
- :greater_than_or_equal_to – Specifies the value must be greater than or equal to the supplied value. The default error message for this option is “must be greater than or equal to %{count}”.
- :equal_to – Specifies the value must be equal to the supplied value. The default error message for this option is “must be equal to %{count}”.
- :less_than – Specifies the value must be less than the supplied value. The default error message for this option is “must be less than %{count}”.
- :less_than_or_equal_to – Specifies the value must be less than or equal the supplied value. The default error message for this option is “must be less than or equal to %{count}”.
- :odd – Specifies the value must be an odd number if set to true. The default error message for this option is “must be odd”. 奇数
- :even – Specifies the value must be an even number if set to true. The default error message for this option is “must be even”. 偶数
The default error message is “is not a number”.
3.9 presence存在
This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.
class
Person
<
ActiveRecord::Base
validates
:name,
:login,
:email,
:presence
=>
true
end
If you want to be sure that an association is present, you’ll need to test whether the foreign key used to map the association is present, and not the associated object itself.
class
LineItem
<
ActiveRecord::Base
belongs_to
:order
validates
:order_id,
:presence
=>
true
end
Since false.blank? is true, if you want to validate the presence of a boolean field you should use validates :field_name, :inclusion => { :in => [true, false] }.
The default error message is “can’t be empty”.
3.10 uniqueness独特性
This helper validates that the attribute’s value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index in your database.
class
Account
<
ActiveRecord::Base
validates
:email,
:uniqueness
=>
true
end
The validation happens by performing an SQL query into the model’s table, searching for an existing record with the same value in that attribute.
There is a :scope范围option that you can use to specify other attributes that are used to limit the uniqueness check:
class
Holiday
<
ActiveRecord::Base
validates
:name,
:uniqueness
=>
{
:scope
=>
:year,
:message
=>
“should
happen
once
per
year”
}
end
There is also a :case_sensitive option that you can use to define whether the uniqueness constraint约束will be case sensitive敏感or not. This option defaults to true.
class
Person
<
ActiveRecord::Base
validates
:name,
:uniqueness
=>
{
:case_sensitive
=>
false
}
end
Note that some databases are configured to perform case-insensitive searches anyway.需要注意的是一些数据库配置为执行区分大小写的搜索。
The default error message is “has already been taken”.
3.11 validates_with通过指定类验证
This helper passes the record to a separate class for validation.
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if record.first_name == “Evil”
record.errors[:base] << “This person is evil”
end
end
end
The validates_with helper takes a class, or a list of classes to use for validation. There is no default error message for validates_with. You must manually add errors to the record’s errors collection in the validator class.
To implement the validate method, you must have a record parameter defined, which is the record to be validated.
Like all other validations, validates_with takes the :if, :unless and :on options. If you pass any other options, it will send those options to the validator class as options:
class Person < ActiveRecord::Base
validates_with GoodnessValidator, :fields => [:first_name, :last_name]
end
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == “Evil” }
record.errors[:base] << “This person is evil”
end
end
end
3.12 validates_each
This helper validates attributes against a block. It doesn’t have a predefined validation function. You should create one using a block, and every attribute passed to validates_each will be tested against it. In the following example, we don’t want names and surnames to begin with lower case.
class Person < ActiveRecord::Base
validates_each :name, :surname do |model, attr, value|
model.errors.add(attr, ‘must start with upper case’) if value =~ /\A[a-z]/
end
end
The block receives the model, the attribute’s name and the attribute’s value. You can do anything you like to check for valid data within the block. If your validation fails, you can add an error message to the model, therefore故making it invalid.
4 Common Validation Options常规验证选项
These are common validation options:
4.1 :allow_nil
The :allow_nil option skips the validation when the value being validated is nil.
class Coffee < ActiveRecord::Base
validates :size, :inclusion => { :in => %w(small medium large),
:message => “%{value} is not a valid size” }, :allow_nil => true
end
:allow_nilisignoredbythepresencevalidator.
4.2 :allow_blank
The :allow_blank option is similar to the :allow_nil option. This option will let validation pass if the attribute’s value is blank?, like nil or an empty string for example.
class Topic < ActiveRecord::Base
validates :title, :length => { :is => 5 }, :allow_blank => true
end
Topic.create(“title” => “”).valid? # => true
Topic.create(“title” => nil).valid? # => true
:allow_blankisignoredbythepresencevalidator.
4.3 :message
As you’ve already seen, the :message option lets you specify the message that will be added to the errors collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper.
4.4 :on
The :on option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be run on save (both when you’re creating a new record and when you’re updating it). If you want to change it, you can use :on => :create to run the validation only when a new record is created or :on => :update to run the validation only when a record is updated.:on 选项让你指定什么时候产生验证。
class Person < ActiveRecord::Base
it will be possible to update email with a duplicated value
validates :email, :uniqueness => true, :on => :create
it will be possible to create the record with a non-numerical age
validates :age, :numericality => true, :on => :update
the default (validates on both create and update)
validates :name, :presence => true, :on => :save
end
5 Conditional Validation有附加条件的验证
Sometimes it will make sense感觉to validate an object just when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string or a Proc. You may use the :if option when you want to specify when the validation should happen. If you want to specify when the validation should not happen, then you may use the :unless option.
有时候验证一个对象的时候如果给你一个判定性的(语句)你会感觉到满足。你可以使用:if和:unless选项达到这样的效果,它可以(判断)一个符号(标记),字符串或者是Proc。
Proc 是Ruby 对block的面向对象的封装。
5.1 Using a Symbol with :if and :unless
You can associate the :if and :unless options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
class Order < ActiveRecord::Base
validates :card_number, :presence => true, :if => :paid_with_card?
def paid_with_card?
payment_type == “card”
end
end
5.2 Using a String with :if and :unless
YoucanalsouseastringthatwillbeevaluatedusingevalandneedstocontainvalidRubycode.You should use this option only when the string represents a really short condition.
class Person < ActiveRecord::Base
validates :surname, :presence => true, :if => “name.nil?”
end
5.3 Using a Proc with :if and :unless
Finally, it’s possible to associate :if and :unless with a Proc object which will be called. Using a Proc object gives you the ability to write an inline condition instead of a separate单独method. This option is best suited for one-liners.
class Account < ActiveRecord::Base
validates :password, :confirmation => true,
:unless => Proc.new { |a| a.password.blank? }
end
5.4 Grouping conditional validations
Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using with_options.
class User < ActiveRecord::Base
with_options :if => :is_admin? do |admin|
admin.validates :password, :length => { :minimum => 10 }
admin.validates :email, :presence => true
end
end
All validations inside of with_options block will have automatically passed the condition :if => :is_admin?
6 Performing Custom Validations执行定制验证
When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.
6.1 Custom Validators定制验证器
Custom validators are classes that extend ActiveModel::Validator. These classes must implement a validate method which takes a record as an argument and performs the validation on it. The custom validator is called using the validates_with method.
class MyValidator < ActiveModel::Validator
def validate(record)
if record.name.starts_with? ‘X’
record.errors[:name] << ‘Need a name starting with X please!’
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
The easiest way to add custom validators for validating individual attributes is with the convenient ActiveModel::EachValidator. In this case, the custom validator class must implement实施落实a validate_each method which takes three arguments: record, attribute and value which correspond对应to the instance, the attribute to be validated and the value of the attribute in the passed instance.
最简单的方法添加(需要)验证个别的属性到定制的validatoras是便捷的使用ActiveModel::EachValidator。在这个案例中,定制的validator类必须落实validate_each方法获得三个参数:与实例对应的record, attribute and value,(它们是)接下来的实例的属性和值
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || “is not an email”)
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
As shown in the example, you can also combine结合standard validations with your own custom validators.
6.2 Custom Methods定制(验证)方法
You can also create methods that verify the state of your models and add messages to the errors collection when they are invalid. You must then register these methods by using one or more of the validate, validate_on_create or validate_on_update class methods, passing in the symbols符号for the validation methods’ names.
你也可以创建方法验证你models的状态以及在被验证的时候添加消息到errors集合。你必须注册这些方法通过使用一个或多个这些方法:validate, validate_on_create or validate_on_update类方法加上验证方法的名字。
You can pass more than one symbol for each class method and the respective各自的validations will be run in the same order as they were registered.你可以添加一个或多个符号给(对应的)每一个类方法并且各自的验证将会按照与注册相同的顺序运行。
class Invoice < ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
if !expiration_date.blank? and expiration_date < Date.today
errors.add(:expiration_date, “can’t be in the past”)
end
end
def discount_cannot_be_greater_than_total_value
if discount > total_value
errors.add(:discount, “can’t be greater than total value”)
end
end
end
Youcanevencreateyourownvalidationhelpersandreusetheminseveraldifferentmodels. For example, an application that manages surveys调查may find it useful to express that a certain某些field corresponds对应to a set of choices:
ActiveRecord::Base.class_eval do
def self.validates_as_choice(attr_name, n, options={})
validates attr_name, :inclusion => { {:in => 1..n}.merge(options) }
end
end
Simply reopen ActiveRecord::Base and define a class method like that. You’d typically put this code somewhere in config/initializers. You can use this helper like this:
class Movie < ActiveRecord::Base
validates_as_choice :rating, 5
end
7 Working with Validation Errors工作与Validation Errors
In addition to除了the valid? and invalid? methods covered earlier, Rails provides a number of methods for working with the errors collection and inquiring疑问about the validity of objects.
Thefollowingisalistofthemostcommonlyusedmethods.PleaserefertotheActiveRecord::Errorsdocumentationforalistofalltheavailablemethods.
7.1 errors
Returns an OrderedHash with all errors. Each key is the attribute name and the value is an array of strings with all errors.
class Person < ActiveRecord::Base
validates :name, :presence => true, :length => { :minimum => 3 }
end
person = Person.new
person.valid? # => false
person.errors
=> {:name => [“can’t be blank”, “is too short (minimum is 3 characters)”]}
person = Person.new(:name => “John Doe”)
person.valid? # => true
person.errors # => []
7.2 errors[]
errors[] is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array.
class Person < ActiveRecord::Base
validates :name, :presence => true, :length => { :minimum => 3 }
end
person = Person.new(:name => “John Doe”)
person.valid? # => true
person.errors[:name] # => []
person = Person.new(:name => “JD”)
person.valid? # => false
person.errors[:name] # => [“is too short (minimum is 3 characters)”]
person = Person.new
person.valid? # => false
person.errors[:name]
=> [“can’t be blank”, “is too short (minimum is 3 characters)”]
7.3 errors.add
The add method lets you manually手动add messages that are related to particular attributes. You can use the errors.full_messages or errors.to_a methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended前置(and capitalized大写). add receives接收the name of the attribute you want to add the message to, and the message itself. #这里只是添加的消息并没有验证
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors.add(:name, “cannot contain the characters !@#%*()_–+=”)
end
end
person = Person.create(:name => “!@#”)
person.errors[:name]
=> [“cannot contain the characters !@#%*()_–+=”]
person.errors.full_messages
=> [“Name cannot contain the characters !@#%*()_–+=”]
Another way to do this is using []= setter
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:name] = “cannot contain the characters !@#%*()_–+=”
end
end
person = Person.create(:name => “!@#”)
person.errors[:name]
=> [“cannot contain the characters !@#%*()_–+=”]
person.errors.to_a
=> [“Name cannot contain the characters !@#%*()_–+=”]
7.4 errors[:base]
You can add error messages that are related to the object’s state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since errors[:base] is an array, you can simply add a string to the array and uses it as the error message.
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:base] << “This person is invalid because …”
end
end
7.5 errors.clear
The clear method is used when you intentionally有意want to clear all the messages in the errors collection. Of course, calling errors.clear upon在…之上an invalid object won’t actually make it valid: the errors collection will now be empty, but the next time you call valid? or any method that tries to save this object to the database, the validations will run again. Ifanyofthevalidationsfail,theerrorscollectionwillbefilledagain.
class Person < ActiveRecord::Base
validates :name, :presence => true, :length => { :minimum => 3 }
end
person = Person.new
person.valid? # => false
person.errors[:name]
=> [“can’t be blank”, “is too short (minimum is 3 characters)”]
person.errors.clear
person.errors.empty? # => true
p.save # => false
p.errors[:name]
=> [“can’t be blank”, “is too short (minimum is 3 characters)”]
7.6 errors.size
The size method returns the total number of error messages for the object.
class Person < ActiveRecord::Base
validates :name, :presence => true, :length => { :minimum => 3 }
end
person = Person.new
person.valid? # => false
person.errors.size # => 3
person = Person.new(:name => “Andrea”, :email => “andrea@example.com”)
person.valid? # => true
person.errors.size # => 0
8 Displaying Validation Errors in the View在视图中显示验证错误(信息)
Rails maintains an official plugin that provides helpers to display the error messages of your models in your view templates. You can install it as a plugin or as a Gem.
8.1 Installing as a plugin
$ rails plugin install git://github.com/joelmoss/dynamic_form.git
8.2 Installing as a Gem
Add this line in your Gemfile:
gem “dynamic_form”
Now you will have access to these two methods in your view templates
8.3 error_messages and error_messages_for
When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance.
class Product < ActiveRecord::Base
validates :description, :value, :presence => true
validates :value, :numericality => true, :allow_nil => true
end
<%= form_for(@product) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :description %><br />
<%= f.text_field :description %>
</p>
<p>
<%= f.label :value %><br />
<%= f.text_field :value %>
</p>
<p>
<%= f.submit “Create” %>
</p>
<% end %>
To get the idea, if you submit the form with empty fields you typically get this back, though styles are indeed missing by default:
You can also use the error_messages_for helper to display the error messages of a model assigned to a view template. It’s very similar to the previous example and will achieve exactly the same result.
<%=
error_messages_for
:product
%>
The displayed text for each error message will always be formed by the capitalized name of the attribute that holds the error, followed by the error message itself.大写错误的属性名,后跟错误消息。
<%= f.error_messages :header_message => “Invalid product!”,
:message => “You’ll need to fix the following fields:”,
:header_tag => :h3 %>
Which results in the following content:
If you pass nil to any of these options, it will get rid of the respective section of the div.
8.4 Customizing the Error Messages CSS定制错误消息的CSS样式
The selectors to customize the style of error messages are:定制错误消息的选择器是:
- field_with_errors – Style for the form fields and labels with errors. Errors的form fields和labels的样式
- #errorExplanation – Style for the div element with the error messages. error消息的div元素样式
- #errorExplanation h2 – Style for the header of the div element. div元素包含的标题样式
- #errorExplanation p – Style for the paragraph that holds the message that appears right below the header of the div element.div元素中出现在标题下方段落的样式
- #errorExplanation ul li – Style for the list items with individual error messages. 错误消息中个别的项目列表样式
Scaffolding脚手架for example generates app/assets/stylesheets/scaffold.css.scss, which later compiles to app/assets/stylesheets/scaffold.css and defines the red-based style you saw above.
The name of the class and the id can be changed with the :class and :id options, accepted by both helpers.
Scaffolding — 基架
基于数据库架构生成网页模板的过程。在ASP .NET 中,动态数据使用基架来简化基于Web 的UI 的生成过程。用户可以通过这种UI 来查看和更新数据库。
8.5 Customizing the Error Messages HTML
By default, form fields with errors are displayed enclosed by a div element with the field_with_errors CSS class. However, it’s possible to override that.
The way form fields with errors are treated is defined by ActionView::Base.field_error_proc. This is a Proc that receives two parameters:
- A string with the HTML tag
- An instance of ActionView::Helpers::InstanceTag.
Here is a simple example where we change the Rails behavior to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a span element with a validation-error CSS class. There will be no div element enclosing the input element, so we get rid of that red border around the text field. You can use the validation-error CSS class to style it anyway you want.
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
if instance.error_message.kind_of?(Array)
%(#{html_tag}<span class=“validation-error”>
{instance.error_message.join(‘,’)}</span>).html_safe
else
%(#{html_tag}<span class=“validation-error”>
{instance.error_message}</span>).html_safe
end
end
This will result in something like the following:
9 Callbacks Overview回调概述
Callbacks are methods that get called at certain moments of an object’s life cycle. With callbacks it’s possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
回调是一种在对象生存周期中的某些情况中(使得对象)被调用的方法。通过callbacks可以运行编写的代码无论Active Record对象是在数据库被创建,保存,删除,验证或者是导入。
9.1 Callback Registration
In order to use the available callbacks, you need to register them. You can do that by implementing实施them as ordinary一般methods, and then using a macro-style class method to register them as callbacks.
class User < ActiveRecord::Base
validates :login, :email, :presence => true
before_validation :ensure_login_has_a_value
protected
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
end
end
end
The macro-style class methods can also receive接收a block. Consider using this style if the code inside your block is so short that it fits in just one line.
class User < ActiveRecord::Base
validates :login, :email, :presence => true
before_create do |user|
user.name = user.login.capitalize if user.name.blank?
end
end
It’s considered考虑good practice to declare callback methods as being protected or private. If left public, they can be called from outside of the model and violate违反the principle of object encapsulation封装.(基于)周密的考虑声明callback方法为protected或者private。如果是public,他们可以被model外调用这样违反了封装对象的原则。
10 Available Callbacks
Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations:
10.1 Creating an Object
- before_validation
- after_validation
- before_save
- before_create
- around_create
- after_create
- after_save
10.2 Updating an Object
- before_validation
- after_validation
- before_save
- before_update
- around_update
- after_update
- after_save
10.3 Destroying an Object
- before_destroy
- after_destroy
- around_destroy
after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.after_save总是在after_create and after_update的后面不管他们的微调用如何。
10.4 after_initialize and after_find
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override覆盖your Active Record initialize method.
The after_find callback will be called whenever Active Record loads a record from the database. after_find is called before after_initialize if both are defined.
The after_initialize and after_find callbacks have no before_* counterparts同行, but they can be registered just like the other Active Record callbacks.
class User < ActiveRecord::Base
after_initialize do |user|
puts “You have initialized an object!”
end
after_find do |user|
puts “You have found an object!”
end
end
>> User.new
You have initialized an object!
=> #<User id: nil>
>> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>
11 Running Callbacks
The following methods trigger callbacks:下面的方法触发回调:
- create
- create!
- decrement! 递减
- destroy
- destroy_all
- increment!
- save
- save!
- save(false)
- toggle!
- update
- update_attribute
- update_attributes
- update_attributes!
- valid?
Additionally, the after_find callback is triggered by the following finder methods:此外,after_find会被下面的查找方法触发:
- all
- first
- find
- find_all_by_attribute
- find_by_attribute
- find_by_attribute!
- last
The after_initialize callback is triggered every time a new object of the class is initialized.当类的一个新的对象被初始化的时候都会触发after_initialize回调。
12 Skipping Callbacks
Just as with validations, it’s also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential潜在implications影响may lead to invalid data.正如验证,它有可能忽略回调。这些方法需要注意使用,然而,由于重要的业务规则和应用程序逻辑可能保持回调。没有明白潜在的影响就绕过它们可能会导致非法数据。
- decrement
- decrement_counter
- delete
- delete_all
- find_by_sql
- increment
- increment_counter
- toggle
- touch
- update_column
- update_all
- update_counters
13 Halting Execution停止执行
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model’s validations, the registered callbacks, and the database operation to be executed.
The whole callback chain链is wrapped in a transaction交易. If any before callback method returns exactly false or raises an exception the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish完成that by raising an exception.
Raising an arbitrary exception may break code that expects save and friends not to fail like that.抛出任意异常可能会打断代码预计的save以及朋友们不想要的失败。The ActiveRecord::Rollback exception is thought precisely正是to tell Active Record a rollback is going on. That one is internally captured but not reraised.
14 Relational Callbacks Callbacks相关
Callbacks work through model relationships, and can even be defined by them. Let’s take an example where a user has many posts. In our example, a user’s posts should be destroyed if the user is destroyed. So, we’ll add an after_destroy callback to the User model by way of its relationship to the Post model.
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
class Post < ActiveRecord::Base
after_destroy :log_destroy_action
def log_destroy_action
puts ‘Post destroyed’
end
end
>> user = User.first
=> #<User id: 1>
>> user.posts.create!
=> #<Post id: 1, user_id: 1>
>> user.destroy
Post destroyed
=> #<User id: 1>
15 Conditional Callbacks有条件的回调
Like in validations, we can also make our callbacks conditional, calling them only when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string or a Proc. You may use the :if option when you want to specify when the callback should get called. If you want to specify when the callback should not be called, then you may use the :unless option.
15.1 Using :if and :unless with a Symbol
You can associate the :if and :unless options with a symbol corresponding to the name of a method that will get called right before the callback. When using the :if option, the callback won’t be executed if the method returns false; when using the :unless option, the callback won’t be executed if the method returns true. This is the most common option. Using this form of registration it’s also possible to register several different methods that should be called to check if the callback should be executed.
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => :paid_with_card?
end
15.2 Using :if and :unless with a String
You can also use a string that will be evaluated using eval and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => “paid_with_card?”
end
15.3 Using :if and :unless with a Proc
Finally, it’s possible to associate :if and :unless with a Proc object. This option is best suited when writing short validation methods, usually one-liners.
class Order < ActiveRecord::Base
before_save :normalize_card_number,
:if => Proc.new { |order| order.paid_with_card? }
end
15.4 Multiple Conditions for Callbacks
When writing conditional callbacks, it’s possible to mix both :if and :unless in the same callback declaration.
class Comment < ActiveRecord::Base
after_create :send_email_to_author, :if => :author_wants_emails?,
:unless => Proc.new { |comment| comment.post.ignore_comments? }
end
16 Callback Classes
Sometimes the callback methods that you’ll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.
Here’s an example where we create a class with an after_destroy callback for a PictureFile model.
class PictureFileCallbacks
def after_destroy(picture_file)
if File.exists?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
When declared inside a class the callback method will receive the model object as a parameter. We can now use it this way:
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks.new
end
Note that we needed to instantiate a new PictureFileCallbacks object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method.
class PictureFileCallbacks
def self.after_destroy(picture_file)
if File.exists?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
If the callback method is declared this way, it won’t be necessary to instantiate a PictureFileCallbacks object.
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks
end
You can declare as many callbacks as you want inside your callback classes.
17 Observers观测者
Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn’t directly related to its purpose, observers allow you to add the same functionality outside of a model. For example, it could be argued that a User model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn’t directly related to your model, you may want to consider creating an observer instead.
Observers与callbacks很相似,但是有着很大不同。鉴于callbacks能够影响model通过代码那与他的目的并没有直接关联,observers允许你在model外添加相同的功能。
17.1 Creating Observers
For example, imagine a User model where we want to send an email every time a new user is created. Because sending emails is not directly related to our model’s purpose, we could create an observer to contain this functionality.
$
rails
generate
observer
User
class UserObserver < ActiveRecord::Observer
def after_create(model)
code to send confirmation email…
end
end
As with callback classes, the observer’s methods receive the observed model as a parameter.
17.2 Registering Observers
Observers are conventionally placed inside of your app/models directory and registered in your application’s config/application.rb file. For example, the UserObserver above would be saved as app/models/user_observer.rb and registered in config/application.rb this way:
#
Activate
observers
that
should
always
be
running
config.active_record.observers
=
:user_observer
As usual, settings in config/environments take precedence优先权over those in config/application.rb. So, if you prefer that an observer doesn’t run in all environments, you can simply register it in a specific environment instead.
17.3 Sharing Observers
By default, Rails will simply strip “Observer” from an observer’s name to find the model it should observe. However, observers can also be used to add behavior to more than one model, and so it’s possible to manually specify the models that our observer should observe.
class MailerObserver < ActiveRecord::Observer
observe :registration, :user
def after_create(model)
code to send confirmation email…
end
end
In this example, the after_create method would be called whenever a Registration or User was created. Note that this new MailerObserver would also need to be registered in config/application.rb in order to take effect.
config.active_record.observers
=
:mailer_observer#
与上面的类名相关
18 Transaction Callbacks
There are two additional callbacks that are triggered by the completion of a database transaction: after_commit and after_rollback. These callbacks are very similar to the after_save callback except that they don’t execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction.
Consider, for example, the previous example where the PictureFile model needs to delete a file after a record is destroyed. If anything raises an exception after the after_destroy callback is called and the transaction rolls back, the file will have been deleted and the model will be left in an inconsistent state. For example, suppose that picture_file_2 in the code below is not valid and the save! method raises an error.
PictureFile.transaction do
picture_file_1.destroy
picture_file_2.save!
end
By using the after_commit callback we can account for this case.
class PictureFile < ActiveRecord::Base
attr_accessor :delete_file
after_destroy do |picture_file|
picture_file.delete_file = picture_file.filepath
end
after_commit do |picture_file|
if picture_file.delete_file && File.exist?(picture_file.delete_file)
File.delete(picture_file.delete_file)
picture_file.delete_file = nil
end
end
end
The after_commit and after_rollback callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don’t interfere with the other callbacks. As such, if your callback code could raise an exception, you’ll need to rescue it and handle it appropriately within the callback.