Подтвердить что ты не робот

Сообщения об ошибках проверки Rails: отображение только одного сообщения об ошибке в поле

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 и перейдите к другому полю.

4b9b3361

Ответ 1

Bert over at 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

Ответ 2

[Обновить] Январь /2013 до Rails 3.2.x - синтаксис обновления; добавить спецификацию

Вдохновленный новыми методами проверки в Rails 3.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

Имо проще:

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

Ответ 4

Я написал специальный помощник

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

а затем я использую его под текстовым полем таким образом

<%= display_error(:password) %>

Ответ 5

Я использую этот код для выпуска 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

Как насчет этого @event.errors[:title].first?

Ответ 7

Как и olovwia ответ:

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

Ответ 8

Добавить метод в класс 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".

Ответ 9

Я бы отображал все сообщения об ошибках в одной строке и в формате предложения. Вы не хотите, чтобы пользователь исправил одну ошибку и получил другую ошибку, о которой он не знал после отправки. Сообщая им, что все правила сохранят клики. С учетом сказанного, вот как я это сделаю:

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

с flash_message_now, определенным в ApplicationController (вы можете добавить его к помощнику)

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

Ответ 10

Или вы можете просто изменить массив (с помощью метода "bang" delete_at), так что все после останова по умолчанию рельсы, 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 %>

Ответ 11

# 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

Мой патч обезьяны ActiveModel::Errors class lib/core_ext/rails/active_model/errors.rb (я использую этот код для выпуска Ruby on Rails 5.0):

module ActiveModel
  class Errors

    # don't add an attribute 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

Я думаю, что самый простой способ - использовать параметр allow_bank. Например, чтобы избежать отображения сообщения о том, что имя слишком короткое, когда поле остается пустым, вы можете сделать следующее:

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