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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2569401/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:29:46  来源:igfitidea点击:

Rails validation error messages: Displaying only one error message per field

ruby-on-railsvalidation

提问by TraderJoeChicago

Rails displays all validation error messages associated with a given field. If I have three validates_XXXXX_of :email, and I leave the field blank, I get three messages in the error list.

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

Example:

例子:

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 %>gives me:

<%= 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)

It is better to display one messages at a time. Is there an easy way to fix this problem? It looks straightforward to have a condition like: If you found an error for :email, stop validating :emailand skip to the other field.

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

采纳答案by Nate Murray

Bert over at RailsForumwrote about this a little while back. He wrote the code below and I added some minor tweaks for it to run on Rails-3.0.0-beta2.

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

Add this to a file called app/helpers/errors_helper.rband simply add helper "errors"to your controller.

将此添加到一个名为的文件中,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

回答by rzar

[Update]Jan/2013 to Rails 3.2.x - update syntax; add spec

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

Inspired by new validationmethods in Rails 3.0 I'm adding this tiny Validator. I call it ReduceValidator.

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

lib/reduce_validator.rb:

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

My Model looking like - notice the :reduce => true:

我的模型看起来像 - 注意: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

Works like a charm in my current Rails Project. The advantageous is, i've put the validator only on a few fields not all.

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

spec/lib/reduce_validator_spec.rb:

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

回答by olownia

Imo simplier is:

Imo 更简单的是:

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

回答by jawaica

How about this @event.errors[:title].first?

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

回答by Maciej Goszczycki

I wrote a custom helper

我写了一个自定义助手

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

and then I use it in view under the text field like so

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

<%= display_error(:password) %>

回答by Michelle Tilley

I use this code for Ruby on Rails 3.0 release, which I put in lib/core_ext/rails/active_model/errors.rb:

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

This is essentially the same code as ActiveModel::Errors#full_messages, but won't show more than one error per attribute. Be sure to require the file (say, in an initializer) and now you can call @model.errors.full_message_per_field do |message| ...

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

回答by Alexander

Similar to olovwia's answer:

类似于olovwia的回答:

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

回答by Frank B

Or you can simply modify the array (with 'bang' method delete_at), so everything after stays default rails, i18n etc.

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

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

Complete working code:

完整的工作代码:

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

回答by Coffee Bite

Add a method to ActiveModel::Errors class

向 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

Add it to a file, like lib/core_ext/rails/active_model/errors.rb. Create a file config/initializers/core_ext.rband add a require "core_ext/rails/active_model/errors.rb"to it.

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

回答by Abdo

I would display all the error messages on one line and in a sentence format. You don't want the user to fix one error and end up having another error he was not aware of after submission. Telling them all the rules will save them clicks. With that said, this is how I'd do it:

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

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

with flash_message_now defined in ApplicationController (you can add it to a helper)

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

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