代码之家  ›  专栏  ›  技术社区  ›  TraderJoeChicago

Rails验证错误消息:每个字段仅显示一条错误消息

  •  31
  • TraderJoeChicago  · 技术社区  · 14 年前

    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/>
    

    <%= error_messages_for :comment %>

    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 ,停止验证 :电子邮件

    13 回复  |  直到 7 年前
        1
  •  11
  •   Nate Murray    14 年前

    伯特在 RailsForum

    将此添加到名为 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
    
        2
  •  34
  •   rzar    11 年前

    [更新]

    灵感来源于 validation 方法在Rails3.0中,我添加了一个小小的验证器。我称之为 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 => 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
    
        3
  •  17
  •   olownia    13 年前

    <% @model.errors.each do |attr, msg| %>
      <%= "#{attr} #{msg}" if @model.errors[attr].first == msg %> 
    <% end %>
    
        4
  •  3
  •   Maciej Goszczycki    12 年前

    我写了一个自定义助手

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

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

    <%= display_error(:password) %>
    
        5
  •  3
  •   Sebastián Palma    7 年前

    @event.errors[:title].first ?

        6
  •  2
  •   Michelle Tilley    14 年前

    我将这段代码用于rubyonrails3.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| ...

        7
  •  2
  •   Community gview    7 年前

    类似 olovwia

    <% @errors.keys.each do |attr| %>
     <%= "#{attr.capitalize} #{@errors[attr].first}."%>
    <% end %>"
    
        8
  •  1
  •   Coffee Bite    12 年前

    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" 去吧。

        9
  •  1
  •   Abdo    11 年前

    我会把所有的错误信息显示在一行中,并以句子的形式显示出来。您不希望用户修复一个错误,并在提交后出现另一个他不知道的错误。告诉他们所有的规则将节省他们点击。话说回来,我就是这么做的:

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

    现在在ApplicationController中定义了flash消息(您可以将其添加到助手中)

    def flash_message_now(type, text)
        flash.now[type] ||= []
        flash.now[type] << text
      end
    
        10
  •  1
  •   Frank B    9 年前

    <% @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 %>
    
        11
  •  0
  •   BobMcgregor    8 年前
    # 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 
    
        12
  •  0
  •   c80609a    7 年前

    我的猴子补丁 ActiveModel::Errors lib/core_ext/rails/active_model/errors.rb

    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 并添加一个请求 core_ext/rails/active_model/errors.rb

        13
  •  -2
  •   Antonio Jha    10 年前

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

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