锁定老帖子 主题:Active Record 错误信息本地化
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-10-12
为了让ActiveRecord错误信息可以本地化,我采用打补丁的方式。查看验证这部分代码,发现格式化字符串分散在各个验证方法中,一一重写不大合算。好在它是调用default_error_messages方法来取得错误信息字符串,于是考虑在这里做点文章。 class ActiveRecord::ValidateError attr_reader :error def initialize(error, format, *args) @error = error @format = format @args = args end def to_s return @format if @args.empty? @format % @args end end class ActiveRecord::Errors def self.default_error_messages def self.default_error_messages @@_error_messages end @@_error_messages = {} @@default_error_messages.each do |key, value| @@_error_messages[key] = ActiveRecord::ValidateError.new(key, value) end @@_error_messages end 这下掐了源头,errors里面不再是字符串,而是ValidateError对象了。由于验证代码中使用"%"来格式化字符串(可查询validates_length_of实现代码),所以ValidateError类也需要实现"%": class ActiveRecord::ValidateError def % (*args) self.class.new(@error, @format, *args) end end 还是返回一个ValidateError对象。 现在测试会发现full_messages里面出错,原因是它使用的是" " + msg,这个msg现在是ValidateError对象了,自然不能这样相加,也没找到好的办法在ValidateError中实现。python和D中似乎可以重写_r方法,在ruby中不知道有没有类似的玩意。既然没找到实现办法,只好把full_messages也重写了: class ActiveRecord::Errors def full_messages full_messages = [] @errors.each_key do |attr| @errors[attr].each do |msg| next if msg.nil? if attr == "base" full_messages << msg.to_s else full_messages << @base.class.human_attribute_name(attr) + " " + msg[b].to_s[/b] end end end full_messages end end 加了这么一点代码也要重写。。。 接着可以考虑增加本地化了,暂时为它加上一个format方法,接受一个本地语言的格式化字符串: class ActiveRecord::ValidateError def format(format) format ||= @format [b]return format if @error == :string[/b] return format if @args.empty? format % @args end end 加粗行是预留的扩展。 现在已经可以实现本地化了: <% zh_cn_error_messages = { :inclusion => "没有包含在列表内", :exclusion => "是保留的", :invalid => "无效", :confirmation => "确认不匹配", :accepted => "必须赋值", :empty => "不能为空", :blank => "不能为空", :too_long => "太长 (最长 %d 个字符)", :too_short => "太短 (最短 %d 个字符)", :wrong_length => "长度错误 (必须 %d 个字符)", :taken => "已经存在", :not_a_number => "不是数字" , :must_number => "必须是数字" } en_error_messages = { :inclusion => "is not included in the list", :exclusion => "is reserved", :invalid => "is invalid", :confirmation => "doesn't match confirmation", :accepted => "must be accepted", :empty => "can't be empty", :blank => "can't be blank", :too_long => "is too long (maximum is %d characters)", :too_short => "is too short (minimum is %d characters)", :wrong_length => "is the wrong length (should be %d characters)", :taken => "has already been taken", :not_a_number => "is not a number" } %> <p><label for="post_title">Title</label><br/> <%= text_field 'post', 'title' %> <%= @post.errors.on(:title).map{|e| e.join("<br />") %></p> <p><label for="post_title">Title</label><br/> <%= text_field 'post', 'title' %> <%= @post.errors.on(:title).map{|e| e.format(zh_cn_error_messages[e.error])}.join("<br />") %></p> 同样的错误信息在同一页面上显示了2种语言。 error_messages_for方法还没有实现本地化,实际上它只是个helper而已,完全可以不用它。考虑到它使用也很广泛,把它也打上补丁吧。这个方法里面有2处字符串被硬编码在里面了,可以考虑提取出来。另外full_messages方法也过早地把错误信息字符串化了,为了不影响原有功能,在重写的error_messages_for里面调用另一个功能相近的方法。 class ActiveRecord::Errors def full_messages_with_key full_messages = [] @errors.each_key do |attr| @errors[attr].each do |msg| next if msg.nil? if attr == "base" full_messages << ["", msg] else full_messages << [@base.class.human_attribute_name(attr), msg] end end end full_messages end end ActionView::Helpers::ActiveRecordHelper.module_eval do def error_messages_for(*params) options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {} objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact count = objects.inject(0) {|sum, object| sum + object.errors.count } error_messages = options[:error_messages] unless count.zero? html = {} [:id, :class].each do |key| if options.include?(key) value = options[key] html[key] = value unless value.blank? else html[key] = 'errorExplanation' end end header_message = options[:caption] || "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved" error_messages = objects.map {|object| object.errors.full_messages_with_key.map {|field, error| content_tag(:li, field + " " + (error_messages.nil? ? error.to_s : error.format(error_messages[error.error]))) } } content_tag(:div, content_tag(options[:header_tag] || :h2, header_message) << content_tag(:p, options[:prompt] || 'There were problems with the following fields:') << content_tag(:ul, error_messages), html ) else '' end end end 现在可以使用了: <% <%= error_messages_for 'post' %> <%= error_messages_for 'post', :error_messages => zh_cn_error_messages, :prompt => "下列字段发生错误:", :caption => "保存帖子时发生了 #{@post.errors.count} 个错误" %> 同样是在同一个页面上显示了2种语言的错误提示。 剩下一点要处理的是Errors#add和Errors#add_to_base,add_to_base不太建议使用,它只有一个参数,要保留原有功能的情况下,扩展的余地较小,还不如增加一个功能多点的方法,暂不去管它了。修改add方法,打算使用errors.add(:title, :must_number, "must number")和errors.add(:title, "must number")这2种用法,前一种支持本地化。由于add_to_base也是调用add,所以只要重写这个就行了。 class ActiveRecord::Errors alias_method :add_old, :add def add(attribute, msg = @@default_error_messages[:invalid], *args) return add_old(attribute, ActiveRecord::ValidateError.new(msg, *args)) if msg.is_a?(Symbol) unless msg.is_a?(ActiveRecord::ValidateError) msg = ActiveRecord::ValidateError.new(:string, msg, *args) end add_old(attribute, msg) end end 测试代码: class Post < ActiveRecord::Base has_many :comments, :dependent => :destroy validates_presence_of :title validates_length_of :title, :in => 3 .. 5 def validate if title.nil? || title.blank? errors.add_to_base("You must specify a name or an email address" ) errors.add(:title, :length_must_great_than, "length must > %d", 3) errors.add(:title, :must_number, "must number") errors.add(:title, "Must Must") end end end views/posts/_form.rhtml <% zh_cn_error_messages = { :inclusion => "没有包含在列表内", :exclusion => "是保留的", :invalid => "无效", :confirmation => "确认不匹配", :accepted => "必须赋值", :empty => "不能为空", :blank => "不能为空", :too_long => "太长 (最长 %d 个字符)", :too_short => "太短 (最短 %d 个字符)", :wrong_length => "长度错误 (必须 %d 个字符)", :taken => "已经存在", :not_a_number => "不是数字" , :must_number => "必须是数字", :length_must_great_than => "必须大于 %d " } en_error_messages = { :inclusion => "is not included in the list", :exclusion => "is reserved", :invalid => "is invalid", :confirmation => "doesn't match confirmation", :accepted => "must be accepted", :empty => "can't be empty", :blank => "can't be blank", :too_long => "is too long (maximum is %d characters)", :too_short => "is too short (minimum is %d characters)", :wrong_length => "is the wrong length (should be %d characters)", :taken => "has already been taken", :not_a_number => "is not a number" } %> <%= error_messages_for 'post' %> <%= error_messages_for 'post', :error_messages => zh_cn_error_messages, :prompt => "下列字段发生错误:", :caption => "保存帖子时发生了 #{@post.errors.count} 个错误" %> <!--[form:post]--> <p><label for="post_title">Title</label><br/> <%= text_field 'post', 'title' %> <%= @post.errors.on(:title).map{|e| e.format(zh_cn_error_messages[e.error])}.join("<br />") if @post.errors.on(:title) %></p> <p><label for="post_title">Title</label><br/> <%= text_field 'post', 'title' %> <%= @post.errors.on(:title).map(&:to_s).join("<br />") if @post.errors.on(:title) %></p> 效果图: 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2006-10-12
格式挺难看呢。。。。
|
|
返回顶楼 | |
发表时间:2006-10-12
好贴,干吗不发布到论坛去?
|
|
返回顶楼 | |
发表时间:2006-10-12
主要是还没写完,能不能写完了再发过去?
---------------------------------- 怎么评论也过来了呢。。 |
|
返回顶楼 | |
发表时间:2006-10-13
好想有一个类似的插件
activeheart |
|
返回顶楼 | |
发表时间:2006-10-13
melin 写道 好想有一个类似的插件
activeheart 按我的理解,activeheart只实现了“汉化”或“日语化”的功能,也就是启动后错误信息只能配置成一种语言,并不算是本地化,这个应该在Agile里配置部分提到过了。 我这里是为本地化提供一个支持,既然同一页面上可以显示不同语言,自然可以根据客户语言显示不同的提示信息。 |
|
返回顶楼 | |
发表时间:2006-10-13
好帖子,应该精华.我目前是直接重写error_messages_for来显示message.茄子的方法是最彻底的
|
|
返回顶楼 | |
发表时间:2006-10-13
好帖子,应该精华
|
|
返回顶楼 | |
发表时间:2006-10-14
我找了一个方法,但此方法有点小Bug,如果qeizi可以看出来改一下,这个方法是不错的方法,我感觉。
module ApplicationHelper def error_messages_for(object_name, options = {}) options = options.symbolize_keys object = instance_variable_get("@#{object_name}") unless object.errors.empty? error_lis = [] object.errors.each{ |key,msg| error_lis << content_tag("li", msg) } content_tag("div", content_tag(options[:header_tag] || "h3","发生#{object.errors.count}个错误" ) + content_tag("ul", error_lis), "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation" ) end end end 这个方法,可以实现中文错误提示,同时不显示字段名了。但确实我试了试有Bug,就是当一个表单还没有填写时,只是被下发时,在标记<div> <%= error_messages_for 'user' %></div>处报错。 提示: #{RAILS_ROOT}/app/helpers/application_helper.rb:6:in `error_messages_for' 可惜呀,可惜。不然这是个非常好的方法了。 对了,这个是放在application.rb里的。 |
|
返回顶楼 | |
发表时间:2006-10-14
jerry 写道 我找了一个方法,但此方法有点小Bug,如果qeizi可以看出来改一下,这个方法是不错的方法,我感觉。
module ApplicationHelper def error_messages_for(object_name, options = {}) options = options.symbolize_keys object = instance_variable_get("@#{object_name}") unless object.errors.empty? error_lis = [] object.errors.each{ |key,msg| error_lis << content_tag("li", msg) } content_tag("div", content_tag(options[:header_tag] || "h3","发生#{object.errors.count}个错误" ) + content_tag("ul", error_lis), "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation" ) end end end 这个方法,可以实现中文错误提示,同时不显示字段名了。但确实我试了试有Bug,就是当一个表单还没有填写时,只是被下发时,在标记<div> <%= error_messages_for 'user' %></div>处报错。 提示: #{RAILS_ROOT}/app/helpers/application_helper.rb:6:in `error_messages_for' 可惜呀,可惜。不然这是个非常好的方法了。 对了,这个是放在application.rb里的。 上面这句话的意思是? 你这个方法是“汉化”吧?你给的出错信息太模糊了,你的第6行是哪一行?抛出的异常是什么?这些都是应该给出的。不过我感觉这类排错应该可以自己解决的,发在这里好像有违这个论坛的宗旨。。 原来以为你的第6行是error_list = [],但这行是不可能出错的。所以猜测可能出错在unless那一行,出错原因应该是nil.errors吧,这种错误应该是error_messages_for使用错误了。如果你的变量名是@post,那么它的使用方法是:error_messages_for('post'),这样还出错的话,直接检查你的@post是不是没给值,去掉补丁测试看看。 |
|
返回顶楼 | |