前言
在我还是一个 Rails 新手的时候,曾纠结过,如何更好的规划显示错误信息,以及实现 I18N。前几天整理一个实习生的代码,发现他对错误信息的规划毫无章法,所以就写下这 blog, Rails 新手可以看看。
基本概念
1
2
3
4
5
6
7
8
9
10
11
12
13
class Account < ActiveRecord :: Base
validates_presence_of :email , :message => "Email 不得为空"
end
account = Account . new
# 此时会调用 account.valid?,该 valid? 会调用所有validate,通过则 true,否则为 false
account . save => false
account . valid? => false
account . errors . empty? => false
account . email = 'linjunzhugg@gmail.com'
account . valid? => true
account . save => true
来看下源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# File activemodel/lib/active_model/validations.rb, line 331
def valid? ( context = nil )
current_context , self . validation_context = validation_context , context
errors . clear
run_validations!
ensure
self . validation_context = current_context
end
# File activemodel/lib/active_model/validations.rb, line 394
def run_validations!
_run_validate_callbacks
errors . empty?
end
从上面可以看到,valid? 会将验证结果放在对象的 errors中,如果对象的 errors 没东西,那么会返回 true,验证通过,返回 false,则验证不通过,save 会失败。
开始实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# user.rb
class Account < ActiveRecord :: Base
# 不要在这里手动写 message,应该在 locals 中的 yml 文件写
# validates_presence_of :email, :message => "Email 不得为空"
validates_presence_of :email
end
# users_controller.rb
def create
@user = User . new ( user_params )
if @user . save
render :index
else
# save 失败后会将错误信息存入 @user.errors
# 就不用自己在这里写错误信息了
render :new
end
end
Rails 有默认的错误信息,我们可以根据需要自定义错误信息
对应错误信息 Key
观看 Wiki: http://guides.ruby-china.org/i18n.html
自定义错误信息
validates_presence_of
的错误信息 key 是 blank
,Active Record 会按照下面的顺序寻找blank
:
1
2
3
4
5
activerecord . errors . models . user . attributes . name . blank
activerecord . errors . models . user . blank
activerecord . errors . messages . blank
errors . attributes . name . blank
errors . messages . blank
如果有继承链,则会往继承链上找:
1
2
3
class Admin < User
validates :name , presence : true
end
1
2
3
4
5
6
7
activerecord . errors . models . admin . attributes . name . blank
activerecord . errors . models . admin . blank
activerecord . errors . models . user . attributes . name . blank
activerecord . errors . models . user . blank
activerecord . errors . messages . blank
errors . attributes . name . blank
errors . messages . blank
那么我们可以在 locales 内的 yml 文件中做文章:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
zh - CN :
activerecord :
attributes :
user :
email : 'Email'
password : '密码'
work_order :
title : '标题'
question : '问题'
errors :
# 执行 errors.full_message 会按照此格式输出
format : ! '%{attribute}%{message}'
# 这里作为通用的错误信息
messages :
blank : "%{attribute} 不能为空"
invalid : "%{attribute} 格式不正确"
taken : "%{attribute} 已经被使用"
# 这里作为针对性的 model 错误信息
models :
user :
attributes :
password :
too_short : '密码 不得小于6位'
Tips
1
2
3
4
5
6
7
8
9
10
11
12
zh - CN :
activerecord :
attributes :
user :
email : 'Email'
avtiverecord :
errors :
messages :
blank : "%{attribute} 不能为空"
不要这么写,这样后面的 activerecord 会覆盖掉前面的东西,而不是作为一个 namespace 的存在。
显示错误信息
我们回到 controller 再看下:
1
2
3
4
5
6
7
8
9
10
# users_controller.rb
def create
@user = User . new ( user_params )
if @user . save
render :index
else
# 直接在 view 层去显示错误信息
render :new
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# users_helper.rb
def show_error model , column
if model . send ( 'errors' ) [ column ][ 0 ]
sanitize "<div class='error-msg'>* #{ model . send ( 'errors' ) [ column ][ 0 ] } </div>"
end
end
# users/new.html.erb
< html >
< body >
oooooooooxxxxxxxxx
# 此时这里就可以按照需求显示出我们在 yml 中自定义的错误信息了
show_error ( @user , :avatar )
< /body>
</ html >
非 Active Record 操控错误信息
1
2
3
4
5
6
7
8
9
10
11
# users_controller.rb
def update
if user_pass?
# 自己在 yml 中定义错误信息
flash [ :success ] = I18n . t ( "web.messages.success" )
else
flash [ :error ] = I18n . t ( "web.messages.failed" )
end
end
自定义 validate 该如何定义错误信息
1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord :: Base
validate :file_size
private
def file_size
if avatar . file . size . to_f / ( 1000 ) > 500
errors [ :avatar ] << I18n . t ( "activerecord.errors.models.user.attribuets.avatar.too_long" )
end
end
end
为何在这里调用 errors[:avatar],错误信息就会放到 @user 中呢?
因为validate
是在@user
进行save
时,才会调用,可以说是实例方法
1
2
3
def file_size
p self => 会发现其实是当前实例 @user
end
这种方式只是简单的将错误信息放入errors
,而且在yml
并不能够获取%{attribute}参数
1
errors [ :avatar ] << I18n . t ( "activerecord.errors.models.user.attribuets.avatar.too_long" )
我们来更进一步:
1
2
3
4
5
6
7
def file_size
if avatar . file . size . to_f / ( 1000 ) > 500
# 这样就会调用到`model`的错误信息 (:column, :event_key)
# 也不用写那么长了
errors [ :avatar ] << errors . generate_message ( :avatar , :too_long )
end
end
移除column错误产生的field_with_errors
在 form 表单中,如果某个 column 有相关的 errors 信息, Rails 会自动在该column
相关的元素父层加多一个 div
1
2
3
4
5
6
< div class = "field_with_errors" >
< label for = "job_client_email" > Email : < /label>
</ div >
< div class = "field_with_errors" >
< input type = "email" value = "wrong email" name = "job[client_email]" id = "job_client_email" >
< /div>
重写掉config/application.rb
1
2
3
4
5
6
7
8
9
# 原先
@field_error_proc = Proc . new { | html_tag , instance |
"<div class= \" field_with_errors \" > #{ html_tag } </div>" . html_safe
}
# 重写后(当前也可以根据需求自己改造)
config . action_view . field_error_proc = Proc . new { | html_tag , instance |
html_tag
}