32 votes

Messages d'erreur de validation Rails: Affichage d'un seul message d'erreur par champ

Rails affiche tous les messages d'erreur de validation associés à un champ donné. Si j'ai trois validates_XXXXX_of :email, et que je laisse le champ vide, j'obtiens trois messages dans la liste d'erreurs.

Exemple :

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

<%= error_messages_for :comment %> me donne :

7 erreurs ont empêché l'enregistrement de ce commentaire

Il y avait des problèmes avec les champs suivants :

Le nom ne peut pas être vide
Le nom est trop court (minimum de 6 caractères)
L'email ne peut pas être vide
L'email est trop court (minimum de 4 caractères)
L'email est invalide
Le texte ne peut pas être vide
Le texte est trop court (minimum de 4 caractères)

Il est préférable d'afficher un message à la fois. Y a-t-il un moyen facile de résoudre ce problème ? Il semble simple d'avoir une condition du type : Si vous trouvez une erreur pour :email, arrêtez la validation de :email et passez au champ suivant.

34voto

rzar Points 671

[Mise à jour] Jan/2013 vers Rails 3.2.x - mise à jour de la syntaxe; ajouter une spécification

Inspiré par les nouvelles méthodes de validation dans Rails 3.0, j'ajoute ce petit validateur. Je l'appelle ReduceValidator.

_lib/reduce_validator.rb_:

# montrer seulement un message d'erreur par champ
#
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

Mon Model ressemble à ceci - remarquez le :reduce => true:

validates :titre, :presence => true, :inclusion => { :in => %w[ M. Mme ] }, :reduce => true
validates :prénom, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true
validates :nom, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true

Fonctionne comme un charme dans mon projet Rails actuel. L'avantage est que j'ai mis le validateur sur seulement quelques champs, pas sur tous.

_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(:nom, "message un")
    item.errors.add(:nom, "message deux")
  end

  it { should have(2).error_on(:nom) }

  it "devrait réduire les messages d'erreur" do
    reduce_validator.validate_each(item, :nom, '')
    should have(1).error_on(:nom)
  end

end

5 votes

En utilisant Rails 3.1... J'ai dû changer la première ligne dans la méthode validate_each en return until record.errors.to_hash.has_key?(attribtue)

17voto

olownia Points 137

Imo plus simple est:

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

1 votes

Semble le plus simple, mais cela vous frappera durement lorsque vous devrez changer l'ordre des valeurs pour les traductions i18n.

0 votes

C'est une bonne solution mais si vous imprimez des erreurs dans la balise* , vous verrez des messages d'erreur vides avec des points. Donc, si vous avez besoin que ce code fonctionne avec la balise

  • , utilisez le code suivant. J'ai modifié le code d'olownia. <% @user.errors.each do |attr, msg| %> <% if @user.errors[attr].first == msg %> <%= "* #{msg} ".html_safe %> <% end %> <% end %>

0 votes

Concatiner l'attribut et le message d'erreur pourrait nous poser des problèmes dans les différentes locales. De plus, l'attribut sera toujours en format de code et non localisé. @model.errors.full_messages nous donnera une chaîne localisée. Pour un attribut spécifique, essayez @model.errors.full_messages_for(:attr_name)

11voto

Nate Murray Points 895

Bert sur RailsForum en a parlé il y a quelque temps. Il a écrit le code ci-dessous et j'ai apporté quelques modifications mineures pour qu'il fonctionne sur Rails-3.0.0-beta2.

Ajoutez ceci à un fichier appelé app/helpers/errors_helper.rb et ajoutez simplement helper "errors" à votre contrôleur.

module ErrorsHelper

  # voir: 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

  ####################
  #
  # ajouté pour afficher les erreurs dans une seule ligne par champ
  #
  ####################
  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

3voto

mgoszcz2 Points 257

J'ai écrit un helper personnalisé

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

et ensuite je l'utilise dans la vue sous le champ de texte comme ceci

<%= display_error(:password) %>

2voto

Brandon Tilley Points 49142

Je utilise ce code pour Ruby ​​on Rails 3.0 sortie, que j'ai mis dans 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

C'est essentiellement le même code que ActiveModel::Errors#full_messages, mais ne montrera pas plus d'une erreur par attribut. Assurez-vous de requérir le fichier (par exemple, dans un initialiseur) et maintenant vous pouvez appeler @model.errors.full_message_per_field do |message| ...

3 votes

Pour toute personne débutant dans le développement Rails 3 (comme moi) pour "requérir le fichier (dans un initialiseur)", ajoutez un fichier arbitraire, "votre_projet.rb", à "votre_projet/config/initializers" avec la ligne require 'core_ext/rails/active_model/errors'.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X