Active Record 验证
就是像下面的似的东西
class
Person < ActiveRecord::Base
validates
:name
, presence:
true
end
Person.create(name:
"John Doe"
).valid?
# => true
Person.create(name:
nil
).valid?
# => false
它是用来保证只有合法的数据才能够进入你的数据库,model层的validation(这里类似于migration 我们多使用英文)是最好的防止用户非法数据进入数据库的方法。
当我们new一个东西的时候,他并没有写入到数据库中,只有我们执行save时才会写入的。我们可以利用new_record来判断是否写入了数据库。而我们的validate就是发生在创建object之后,写入数据库之前。假如validate不通过,那么就返回一个invalid,并且不写入数据库。
下面这些函数只有数据valid的时候才会写入数据库,感叹号和非感叹号的区别就是在于感叹号数据非法时抛异常,而非感叹号返回false或者object本身
-
create
-
create!
-
save
-
save!
-
update
-
update!
-
decrement!
-
decrement_counter
-
increment!
-
increment_counter
-
toggle!
-
touch
-
update_all
-
update_attribute
-
update_column
-
update_columns
-
update_counters
-
save(validate: false)
如何判断object是否valid呢,我们使用的方法是valid?和invalid?用法如下
class
Person < ActiveRecord::Base
validates
:name
, presence:
true
end
Person.create(name:
"John Doe"
).valid?
# => true
Person.create(name:
nil
).valid?
# => false
|
假如create的非法,我们需要知道错误信息,那么我们就要使用errors返回所有的错误信息了,他是一个hash,key为错误的列的名字,data为错误信息。所以我们可以用errors[:name]得到关于name的错误信息。假如我们的create合法,那么errors为空
class
Person < ActiveRecord::Base
validates
:name
, presence:
true
end
>> Person.
new
.errors[
:name
].any?
# => false
>> Person.create.errors[
:name
].any?
# => true
|
Validation Helpers
上面讲的很多东西都很是理论的,下面讲的东西将是真正的一些函数的使用。注意,helper在rails中有小工具的意思,你可以直接使用。
这些helper有一个共同的特点,他们都在你的model上添加上相应的validate,假如不通过那么就会在errors中加入错误信息。并且他们都可以接受:on 和 :message 两个参数,on参数决定这个validation是施加在哪个操作上面的,默认的是:save 还可以是 :create 或者 :update ;而 :message参数则决定发生错误往errors里面写入的信息
他们的形式都是类似这样子的
validates
:terms_of_service
, acceptance:
true
validates 调用函数,:terms_of_service 是你列的名字,而acceptance:true是一个key =》 data 键值对形式,表明了helper使用的类型,下面一个个的说说
acceptance
这个申请某个变量可接受的数据,它并不写入数据库,可以参考我们要求用户同意某种服务款项什么的,要求用户同意,但是我们不用把它写入数据库。
validates
:terms_of_service
, acceptance:
true
这个表明terms_of_service 这个参数只接受true
class
Person < ActiveRecord::Base
validates
:terms_of_service
, acceptance: { accept:
'yes'
}
end
|
而这个表明terms_of_service接受yes这个字符串,也就是说们key => data(这个是个hash对,ruby中可以用key:data表示)data也可以是一个hash,里面的accept key 表明了可以接受的参数形式
validates_associated
用来表示你的模型和其他模型有关联关系,你的模型包含着其他的模型,就例如下面的
class
Library < ActiveRecord::Base
has_many
:books
validates_associated
:books
end
confirmation
class
Person < ActiveRecord::Base
validates
:email
, confirmation:
true
end
|
这个是什么意思呢,他就是我们网页中需要输入两次相同信息的部分,比如初始化密码的设置,需要两次输入的一样什么的。它会自动和email_confirmation 这个参数比较
我们在view层可以这样用
<%=
text_field
:person
,
:email
%>
<%=
text_field
:person
,
:email_confirmation
%>
而在model定义的时候,我们要加上这个
class
Person < ActiveRecord::Base
validates
:email
, confirmation:
true
validates
:email_confirmation
, presence:
true
end
|
就是简单的加上了email_confirmation必须出现
exclusion
除了的意思,验证数据的值没有在参数里面的数据出现。
class
Account < ActiveRecord::Base
validates
:subdomain
, exclusion: {
in
: %w(www us ca jp),
message:
"Subdomain %{value} is reserved."
}
end
|
inclusion
和上面的整好相反,数据的值要出现在参数的数据中,用法相同,同样in 可以换位within
class
Coffee < ActiveRecord::Base
validates
:size
, inclusion: {
in
: %w(small medium large),
message:
"%{value} is not a valid size"
}
end
format
定义可接受数据的格式
class
Product < ActiveRecord::Base
validates
:legacy_code
, format: { with: /\
A
[a-zA-
Z
]+\z/,
message:
"Only letters allowed"
}
end
|
同样format指向一个hash,里面有一个with key 它指向一个正则表达式
length
这个是参数可接受的长度,这个并不一定是固定长度,他也是指向一个hash,可以进行各种设置
class
Person < ActiveRecord::Base
validates
:name
, length: { minimum:
2
} #最小
validates
:bio
, length: { maximum:
500
} #最大
validates
:password
, length: {
in
:
6
..
20
} #在6-20范围内
validates
:registration_number
, length: { is:
6
} #恰好长度为6
end
length指向的hash 主要包含以上几种,可以同时使用,用逗号分开即可。
它的错误信息除了可以用:message指定外,还可以用:wrong_length 、 too_long 、too_short指定,再这些信息中可以使用%{count}代替指定的长度,在too_long中它就显示最大的长度,too_short中就显示最小的长度。
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"
, #count = 300
too_long:
"must have at most %{count} words" #count = 400
}
end
|
numericality
它约定你的参数只能是数字,这里数字指整数和浮点数。假如你numericality指向一个hash,里面only_integer 设置为true,那么他就仅接受整数了。
用法如下
class
Player < ActiveRecord::Base
validates
:points
, numericality:
true
validates
:games_played
, numericality: { only_integer:
true
}
end
|
除了only_integer,他还接受
-
:greater_than
- 大于 -
:greater_than_or_equal_to
- 大于等于 -
:equal_to
- 等于 -
:less_than
-小于 -
:less_than_or_equal_to
-小于等于 -
:odd
- 如果设置为true,那么它仅接受奇数 -
:even
-如果设置为true,那么它仅接受偶数
presence
不能为空
class
Person < ActiveRecord::Base
validates
:name
,
:login
,
:email
, presence:
true
end
|
absence
为空
class
Person < ActiveRecord::Base
validates
:name
,
:login
,
:email
, absence:
true
end
|
uniqueness
唯一的,他并不是给数据库列加上唯一约束,而是通过在存储前查找实现的。
class
Account < ActiveRecord::Base
validates
:email
, uniqueness:
true
end
|
uniqueness也可以指向一个hash,常用的是忽略大小写敏感用case_sensitive
class
Person < ActiveRecord::Base
validates
:name
, uniqueness: { case_sensitive:
false
}
end
|
Common Validation Options
一般的validation选项
allow_nil
允许空,它是通过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_blank
允许blank,blank是rails中的函数,“”.blank? == true, "".nil? == flase 这是blank?和nil?最大的区别,blank?关心的是内容,而nil更注重这个object是否存在。
class
Topic < ActiveRecord::Base
validates
:title
, length: { is:
5
}, allow_blank:
true
end
Topic.create(
"title"
=>
""
).valid?
# => true
Topic.create(
"title"
=>
nil
).valid?
# => true
strict validations
假如我们需要在validation不满足时不仅仅是一个异常,而是抛出一个错误,我们可以设置:strict为true,当然我们还可以用strict来设置自己的异常处理函数。当不满足validation时我们自行进行处理。
class
Person < ActiveRecord::Base
validates
:name
, presence: { strict:
true
}
end
Person.
new
.valid?
# => ActiveModel::StrictValidationFailed: Name can't be blank
|
class
Person < ActiveRecord::Base
validates
:token
, presence:
true
, uniqueness:
true
, strict: TokenGenerationException
end
Person.
new
.valid?
# => TokenGenerationException: Token can't be blank
|
Conditional Validation
条件validation,就是使用if和unless判断断言满足或者不满足。
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.
这个主要说的是你可以用一个函数的符号放在if后面,然后你在声明这个函数,这个函数需要返回true或false
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
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.
你可以在if后面放一个string,这个string可以是一段可以在irb执行的ruby代码
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.
可以在后面放置一个proc对象。
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
.
还可以将所有的if或者unless约束打组写出,以前我们一个个为参数约定变量时,我们用validates,现在我们可以使用with_options 对所有需要if或者unless的参数打组约定。这样,所有在with_options中的参数都会自动的加上if: :is_admin?约束(就是指向符号的约束)。
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?
5.5 Combining Validation Conditions
On the other hand, when multiple conditions define whether or not a validation should happen, an Array
can be used. Moreover, you can apply both :if
and :unless
to the same validation.
假如你有多个if或者unless约束,你可以将他们放在一个数组中,然后用一个if或者unless约束就好啦
class
Computer < ActiveRecord::Base
validates
:mouse
, presence:
true
,
if
: [
"market.retail?"
,
:desktop
?]
unless
:
Proc
.
new
{ |c| c.trackpad.present? }
end
|
The validation only runs when all the :if
conditions and none of the :unless
conditions are evaluated to true
.
Performing Custom Validations
当默认的validations helper无法满足你的需要时,你可以定义自己的validation。用法很简单,你需要继承ActiveModel::Validator类重写其中的validate
方法。你约束时用validates_with
就可以了,在后面写上你自己定义的class名字。validate函数返回的erros
class
MyValidator < ActiveModel::Validator
def
validate(record)
unless
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
|
很简单吧,其实还有个更简单的办法,你可以像一般函数一样使用这些自定义的validations。就是你继承自ActiveModel::EachValidator
然后重写里面的validate_each函数,以后就可以用了。下面有一个例子。
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
|
你看他的明明方式还是很有特点的,你的class 叫 XValidator 那么你的约束就是叫 x 比如 EmailValidator 就是:email
最后,一些没讲的东西,我感觉用处不大的,我没有再翻译,仅粘贴英文
6.2 Custom Methods(这个是指直接用方法约束你的参数)
You can also create methods that verify the state of your models and add messages to the error s
collection when they are invalid. You must then register these methods by using the validate
class method, passing in the symbols for the validation methods' names.
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.present? && 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
|
By default such validations will run every time you call valid?
. It is also possible to control when to run these custom validations by giving an :on
option to the validate
method, with either: :create
or :update
.
class
Invoice < ActiveRecord::Base
validate
:active_customer
, on:
:create
def
active_customer
errors.add(
:customer_id
,
"is not active"
)
unless
customer.active?
end
end
|
7 Working with Validation Errors(各种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.
The following is a list of the most commonly used methods. Please refer to the ActiveModel::Errors
documentation for a list of all the available methods.
7.1 errors
Returns an instance of the class ActiveModel::Errors
containing 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 it and it will be used as an 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. If any of the validations fail, the errors
collection will be filled again.
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
# => 2
person = Person.
new
(name:
"Andrea"
, email:
"andrea@example.com"
)
person.valid?
# => true
person.errors.size
# => 0
|
8 Displaying Validation Errors in Views
Once you've created a model and added validations, if that model is created via a web form, you probably want to display an error message when one of the validations fail.
Because every application handles this kind of thing differently, Rails does not include any view helpers to help you generate these messages directly. However, due to the rich number of methods Rails gives you to interact with validations in general, it's fairly easy to build your own. In addition, when generating a scaffold, Rails will put some ERB into the _form.html.erb
that it generates that displays the full list of errors on that model.
Assuming we have a model that's been saved in an instance variable named @post
, it looks like this:
<%
if
@post
.errors.any? %>
<div id=
"error_explanation"
>
<h2><%= pluralize(
@post
.errors.count,
"error"
) %> prohibited this post from being saved:</h2>
<ul>
<%
@post
.errors.full_messages.
each
do
|msg| %>
<li><%= msg %></li>
<%
end
%>
</ul>
</div>
<%
end
%>
|
Furthermore, if you use the Rails form helpers to generate your forms, when a validation error occurs on a field, it will generate an extra <div>
around the entry.
<div class="field_with_errors">
<input id="post_title" name="post[title]" size="30" type="text" value="">
</div>
|
You can then style this div however you'd like. The default scaffold that Rails generates, for example, adds this CSS rule:
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
|
This means that any field with an error ends up with a 2 pixel red border.