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

时间:2010-04-02 21:52:56

标签: ruby-on-rails validation

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错误,请停止验证:email并跳到其他字段。

13 个答案:

答案 0 :(得分:34)

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

受Rails 3.0中新的validation方法的启发,我正在添加这个微小的Validator。我称之为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

答案 1 :(得分:16)

Imo简化是:

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

答案 2 :(得分:10)

伯特在RailsForum过了一会儿写了这篇文章。他编写了下面的代码,并添加了一些小调整,以便在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

答案 3 :(得分:3)

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

答案 4 :(得分:3)

我写了一个自定义助手

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

然后我在文本字段中的视图中使用它,如此

<%= display_error(:password) %>

答案 5 :(得分:2)

我将此代码用于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| ...

答案 6 :(得分:2)

olovwia的答案类似:

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

答案 7 :(得分:1)

向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"

答案 8 :(得分:1)

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

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

答案 9 :(得分:0)

或者你可以简单地修改数组(使用'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 %>

答案 10 :(得分:0)

# 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 

答案 11 :(得分:0)

我的ActiveModel::Errorslib/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并向其添加需求core_ext/rails/active_model/errors.rb

答案 12 :(得分:-2)

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

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