【问题标题】:Rails validation error messages: Displaying only one error message per fieldRails 验证错误消息:每个字段仅显示一条错误消息
【发布时间】:2011-02-03 21:25:53
【问题描述】:

Rails 显示与给定字段关联的所有验证错误消息。如果我有三个validates_XXXXX_of :email,并且将该字段留空,则会在错误列表中收到三条消息。

例子:

validates_presence_of :name
validates_presence_of :email
validates_presence_of :text

validates_length_of :name, :in => 6..30
validates_length_of :email, :in => 4..40
validates_length_of :text, :in => 4..200

validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i<br/>

&lt;%= error_messages_for :comment %&gt; 给我:

7 errors prohibited this comment from being saved

There were problems with the following fields:

Name can't be blank
Name is too short (minimum is 6 characters)
Email can't be blank
Email is too short (minimum is 4 characters)
Email is invalid
Text can't be blank
Text is too short (minimum is 4 characters)

最好一次显示一条消息。有没有简单的方法来解决这个问题?有这样一个条件看起来很简单:如果您发现 :email 有错误,请停止验证 :email 并跳到另一个字段。

【问题讨论】:

    标签: ruby-on-rails validation


    【解决方案1】:

    这个怎么样 @event.errors[:title].first?

    【讨论】:

      【解决方案2】:

      我的 ActiveModel::Errors 类 lib/core_ext/rails/active_model/errors.rb 的猴子补丁(我将此代码用于 Ruby on Rails 5.0 版本):

      module ActiveModel
        class Errors
      
          # don't add an attribute's error message to details
          # if it already contains at least one message
      
          alias_method :old_add, :add
      
          def add(attribute, message = :invalid, options = {})
            if details[attribute.to_sym].size.zero?
              old_add(attribute, message, options)
            end
          end
      
        end
      end
      

      创建一个文件config/initializers/core_ext.rb 并向其中添加一个require core_ext/rails/active_model/errors.rb。

      【讨论】:

        【解决方案3】:
        # Extracts at most <strong>one error</strong> message <strong>per field</strong> from the errors-object.
        # @param  [ActiveModel::Errors] the_errors_object The errors-object.
        # @raise  [ArgumentError] If the given argument is not an instance of ActiveModel::Errors.
        # @return [Array] A string-array containing at most one error message per field from the given errors-object.
        def get_one_error_per_field(the_errors_object)
          if the_errors_object.is_a? ActiveModel::Errors    
            errors = {}  
            the_errors_object.each do |field_name, associated_error|
              errors[field_name] = the_errors_object.full_message(field_name, associated_error) unless errors[field_name]
            end 
            return errors.values
          else
            raise ArgumentError.new('The given argument isn\'t an instance of ActiveModel::Errors!')
          end 
        end 
        

        【讨论】:

          【解决方案4】:

          或者您可以简单地修改数组(使用 'bang' 方法 delete_at),因此之后的所有内容都保持默认 rails、i18n 等。

          <% @article.errors.keys.each { |attr| @article.errors[attr].delete_at(1) } %> 
          

          完整的工作代码:

          <% if @article.errors.any? %>
            <% @article.errors.keys.each { |attr| @article.errors[attr].delete_at(1) } %> 
             <ul>
              <% @article.errors.full_messages.each do |msg| %>
               <li><%= msg %></li>
              <% end %>
            </ul>
          <% end %>
          

          【讨论】:

            【解决方案5】:

            类似于olovwia的回答:

            <% @errors.keys.each do |attr| %>
             <%= "#{attr.capitalize} #{@errors[attr].first}."%>
            <% end %>"
            

            【讨论】:

              【解决方案6】:

              我认为最简单的方法是使用 allow_bank 选项。 例如,为避免在字段留空时显示名称太短的消息,您可以执行以下操作:

              validates_length_of :name, allow_blank:true, :in => 6..30
              

              【讨论】:

                【解决方案7】:

                我会在一行中以句子格式显示所有错误消息。您不希望用户修复一个错误并最终遇到另一个他在提交后不知道的错误。告诉他们所有规则将为他们节省点击次数。话虽如此,这就是我的做法:

                flash_message_now("error", 
                   @album.errors.keys.map { |k| "#{Album.human_attribute_name(k)} #{@album.errors[k].to_sentence}"}.to_sentence
                )
                

                在 ApplicationController 中定义了 flash_message_now(您可以将其添加到帮助程序中)

                def flash_message_now(type, text)
                    flash.now[type] ||= []
                    flash.now[type] << text
                  end
                

                【讨论】:

                  【解决方案8】:

                  [更新] 2013 年 1 月至 Rails 3.2.x - 更新语法;添加规格

                  受 Rails 3.0 中新的 validation 方法的启发,我添加了这个微型验证器。我叫它ReduceValidator。

                  lib/reduce_validator.rb:

                  # show only one error message per field
                  #
                  class ReduceValidator < ActiveModel::EachValidator
                  
                    def validate_each(record, attribute, value)
                      return until record.errors.messages.has_key?(attribute)
                      record.errors[attribute].slice!(-1) until record.errors[attribute].size <= 1
                    end
                  end
                  

                  我的模型看起来像 - 注意 :reduce =&gt; true:

                  validates :title, :presence => true, :inclusion => { :in => %w[ Mr Mrs ] }, :reduce => true
                  validates :firstname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true
                  validates :lastname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true
                  

                  在我当前的 Rails 项目中就像一个魅力。 好处是,我只将验证器放在了几个字段上,而不是全部。

                  spec/lib/reduce_validator_spec.rb:

                  require 'spec_helper'
                  
                  describe ReduceValidator do
                  
                    let(:reduce_validator) { ReduceValidator.new({ :attributes => {} }) }
                  
                    let(:item) { mock_model("Item") }
                    subject { item }
                  
                    before(:each) do
                      item.errors.add(:name, "message one")
                      item.errors.add(:name, "message two")
                    end
                  
                    it { should have(2).error_on(:name) }
                  
                    it "should reduce error messages" do
                      reduce_validator.validate_each(item, :name, '')
                      should have(1).error_on(:name)
                    end
                  
                  end
                  

                  【讨论】:

                  • 使用 Rails 3.1... 我不得不将 validate_each 方法中的第一行更改为 return until record.errors.to_hash.has_key?(attribtue)
                  【解决方案9】:

                  向 ActiveModel::Errors 类添加方法

                  module ActiveModel
                    class Errors
                      def full_unique_messages
                        unique_messages = messages.map { |attribute, list_of_messages| [attribute, list_of_messages.first] }
                        unique_messages.map { |attribute_message_pair| full_message *attribute_message_pair }
                      end
                    end
                  end
                  

                  将其添加到文件中,例如 lib/core_ext/rails/active_model/errors.rb。创建一个文件config/initializers/core_ext.rb 并向其中添加require "core_ext/rails/active_model/errors.rb"。

                  【讨论】:

                    【解决方案10】:

                    我写了一个自定义助手

                    def display_error(field)
                        if @user.errors[field].any?
                            raw @user.errors[field].first+"<br>"
                        end
                    end
                    

                    然后我像这样在文本字段下的视图中使用它

                    <%= display_error(:password) %>
                    

                    【讨论】:

                      【解决方案11】:

                      Imo 更简单的是:

                      <% @model.errors.each do |attr, msg| %>
                        <%= "#{attr} #{msg}" if @model.errors[attr].first == msg %> 
                      <% end %>
                      

                      【讨论】:

                      • 看起来最简单,但是当您需要更改 i18n 翻译值的顺序时,这将给您带来很大的打击。
                      • 这是一个很好的解决方案,但是如果您在
                      • 标记中打印错误,您将看到带有点的空白错误消息。因此,如果你们需要此代码来处理
                      • 标签,请使用以下代码。我修改了olownia的代码。
                          #{msg}
                      • ".html_safe %>
                      【解决方案12】:

                      我将此代码用于 Ruby on Rails 3.0 版本,我将其放入 lib/core_ext/rails/active_model/errors.rb:

                      module ActiveModel
                        class Errors
                          def full_message_per_field
                            messages_per_field = []
                            handled_attributes = []
                      
                            each do |attribute, messages|
                              next if handled_attributes.include? attribute
                              messages = Array.wrap(messages)
                              next if messages.empty?
                      
                              if attribute == :base
                                messages_per_field << messages.first
                              else
                                attr_name = attribute.to_s.gsub('.', '_').humanize
                                attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
                                options = { :default => "%{attribute} %{message}", :attribute => attr_name }
                      
                                messages_per_field << I18n.t(:"errors.format", options.merge(:message => messages.first))
                              end
                      
                              handled_attributes << attribute
                            end
                      
                            messages_per_field
                          end
                        end
                      end
                      

                      这与ActiveModel::Errors#full_messages 本质上是相同的代码,但每个属性不会显示一个以上的错误。确保需要该文件(例如,在初始化程序中),现在您可以调用 @model.errors.full_message_per_field do |message| ...

                      【讨论】:

                      • 对于 Rails 3 开发新手(比如我)“需要文件(比如在初始化程序中)”添加任意文件“your_project.rb”到“your_project/config/initializers”行require 'core_ext/rails/active_model/errors'。
                      【解决方案13】:

                      RailsForum 的 Bert 不久前写过这篇文章。他写了下面的代码,我添加了一些小的调整让它在 Rails-3.0.0-beta2 上运行。

                      将此添加到名为app/helpers/errors_helper.rb 的文件中,然后将helper "errors" 添加到您的控制器中。

                      module ErrorsHelper
                      
                        # see: lib/action_view/helpers/active_model_helper.rb
                        def error_messages_for(*params)
                              options = params.extract_options!.symbolize_keys
                      
                              objects = Array.wrap(options.delete(:object) || params).map do |object|
                                object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
                                object = convert_to_model(object)
                      
                                if object.class.respond_to?(:model_name)
                                  options[:object_name] ||= object.class.model_name.human.downcase
                                end
                      
                                object
                              end
                      
                              objects.compact!
                              count = objects.inject(0) {|sum, object| sum + object.errors.count }
                      
                              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
                                options[:object_name] ||= params.first
                      
                                I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
                                  header_message = if options.include?(:header_message)
                                    options[:header_message]
                                  else
                                    locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
                                  end
                      
                                  message = options.include?(:message) ? options[:message] : locale.t(:body)
                      
                                  error_messages = objects.sum do |object|
                                    object.errors.on(:name)
                                    full_flat_messages(object).map do |msg|
                                      content_tag(:li, ERB::Util.html_escape(msg))
                                    end
                                  end.join.html_safe
                      
                                  contents = ''
                                  contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
                                  contents << content_tag(:p, message) unless message.blank?
                                  contents << content_tag(:ul, error_messages)
                      
                                  content_tag(:div, contents.html_safe, html)
                                end
                              else
                                ''
                              end
                        end
                      
                        ####################
                        #
                        # added to make the errors display in a single line per field
                        #
                        ####################
                        def full_flat_messages(object)
                          full_messages = []
                      
                          object.errors.each_key do |attr|
                            msg_part=msg=''
                            object.errors[attr].each do |message|
                              next unless message
                              if attr == "base"
                                full_messages << message
                              else
                                msg=object.class.human_attribute_name(attr)
                                msg_part+= I18n.t('activerecord.errors.format.separator', :default => ' ') + (msg_part=="" ? '': ' & ' ) + message
                              end
                            end
                            full_messages << "#{msg} #{msg_part}" if msg!=""
                          end
                          full_messages
                        end
                      
                      end
                      

                      【讨论】:

                        猜你喜欢
                        相关资源
                        最近更新 更多
                        热门标签