Message d'erreur de validation entièrement personnalisé avec Rails

260

En utilisant Rails, j'essaie d'obtenir un message d'erreur du type "Le champ de la chanson ne peut pas être vide" lors de l'enregistrement. Procédez comme suit:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

... affiche uniquement "Song Rep XYW ne peut pas être vide", ce qui n'est pas bon car le titre du champ n'est pas convivial. Comment puis-je changer le titre du champ lui-même? Je pourrais changer le nom réel du champ dans la base de données, mais j'ai plusieurs champs "chanson" et j'ai besoin d'avoir des noms de champs spécifiques.

Je ne veux pas contourner le processus de validation des rails et je pense qu'il devrait y avoir un moyen de résoudre ce problème.

marcgg
la source

Réponses:

432

Désormais, la méthode acceptée pour définir les noms humanisés et les messages d'erreur personnalisés consiste à utiliser des paramètres régionaux .

# config/locales/en.yml
en:
  activerecord:
    attributes:
      user:
        email: "E-mail address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required"

Maintenant, le nom humanisé et le message de validation de présence pour l'attribut "email" ont été modifiés.

Les messages de validation peuvent être définis pour un modèle + attribut spécifique, un modèle, un attribut ou globalement.

graywh
la source
19
Si vous utilisez mongoid, remplacez activerecord: par mongoid:
Intentss
88
@graywh: Où les questions sur une réponse devraient-elles être publiées, sinon dans les commentaires? Voici le guide I18n: guides.rubyonrails.org/i18n.html
Tyler Rick
4
Soit dit en passant: si vous passez un symbole pour le paramètre de message de votre validateur dans Rails 3.1.3, il vous indiquera l'étendue qu'il recherchait car il ne sera pas trouvé, donc vous savez exactement quoi mettre dans votre locales yml.
aceofspades
4
Eh bien, c'est bien et tout, mais que se passe-t-il si le fait de rajouter naïvement le nom de la colonne (aussi lisible soit-il) conduirait à une grammaire complètement mise à jour (en particulier dans les langues non anglaises)? Dois-je vraiment utiliser errors.add :base, msg? Je voudrais savoir de quelle colonne il s'agit, afin que je puisse l'afficher dans le champ de formulaire correct.
panzi
6
@graywh Peut-être qu'il me manque quelque chose, mais ne fait-il pas toujours précéder le nom de la colonne avant le message? Même en anglais, j'aimerais avoir par exemple The password is wrong.ou The email address is not valid.au lieu de Password is wrong.et Email is not valid..
panzi
65

Dans votre modèle:

validates_presence_of :address1, message: 'Put some address please' 

À votre avis

<% m.errors.each do |attr, msg|  %>
 <%= msg %>
<% end %>

Si vous le faites à la place

<%= attr %> <%= msg %>

vous obtenez ce message d'erreur avec le nom de l'attribut

address1 Put some address please

si vous voulez obtenir le message d'erreur pour un seul attribut

<%= @model.errors[:address1] %>
Federico
la source
Ce n'est pas une solution acceptable. Et si je veux le comportement par défaut pour tous les autres attributs (attr + msg)?
Rômulo Ceccon
Voilà, vous pouvez jouer avec ces 2 choses et le faire fonctionner
Federico
Vous devez utiliser un symbole pour qu'il apparaisse dans vos fichiers yml, commevalidates_presence_of :address1, :message => :put_some_address_please
Federico
Ce n'est pas acceptable, car le nom du champ est inclus
fatuhoku
62

Essaye ça.

class User < ActiveRecord::Base
  validate do |user|
    user.errors.add_to_base("Country can't be blank") if user.country_iso.blank?
  end
end

J'ai trouvé ça ici .

Voici une autre façon de procéder. Ce que vous faites est de définir une méthode human_attribute_name sur la classe de modèle. La méthode reçoit le nom de colonne sous forme de chaîne et renvoie la chaîne à utiliser dans les messages de validation.

class User < ActiveRecord::Base

  HUMANIZED_ATTRIBUTES = {
    :email => "E-mail address"
  }

  def self.human_attribute_name(attr)
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end

end

Le code ci-dessus est d' ici

Maulin
la source
Le problème est que mon champ s'appelle: song_rep_xyz (enfin, quelque chose de compliqué), qui n'est pas convivial
marcgg
16
pour Rails 3, "def self.human_attribute_name (attr)" doit être changé en "def self.human_attribute_name (attr, options = {})", sinon il renvoie une erreur
spacemonkey
3
Merci pour cela. J'avais besoin de quelque chose qui fonctionnait pour Rails 2. (Oui, pauvre moi ... :)
Dan Barron
18

Oui, il existe un moyen de le faire sans le plugin! Mais ce n'est pas aussi propre et élégant que d'utiliser le plugin mentionné. C'est ici.

En supposant que c'est Rails 3 (je ne sais pas si c'est différent dans les versions précédentes),

gardez ceci dans votre modèle:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

et dans la vue, au lieu de partir

@instance.errors.full_messages

comme ce serait le cas lorsque nous utilisons le générateur d'échafaudage, mettez:

@instance.errors.first[1]

Et vous obtiendrez juste le message que vous avez spécifié dans le modèle, sans le nom de l'attribut.

Explication:

#returns an hash of messages, one element foreach field error, in this particular case would be just one element in the hash:
@instance.errors  # => {:song_rep_xyz=>"can't be empty"}

#this returns the first element of the hash as an array like [:key,"value"]
@instance.errors.first # => [:song_rep_xyz, "can't be empty"]

#by doing the following, you are telling ruby to take just the second element of that array, which is the message.
@instance.errors.first[1]

Jusqu'à présent, nous n'affichons qu'un seul message, toujours pour la première erreur. Si vous voulez afficher toutes les erreurs, vous pouvez faire une boucle dans le hachage et afficher les valeurs.

J'espère que cela a aidé.

Marco Antonio
la source
16

Code Rails3 avec des messages entièrement localisés:

Dans le modèle user.rb définissez la validation

validates :email, :presence => true

Dans config / locales / en.yml

en:  
  activerecord:
    models: 
      user: "Customer"
    attributes:
      user:
        email: "Email address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "cannot be empty"
Lukas
la source
15

Dans la méthode de validation personnalisée, utilisez:

errors.add(:base, "Custom error message")

car add_to_base est obsolète.

errors.add_to_base("Custom error message")

amit_saxena
la source
13

Lié à la réponse acceptée et à une autre réponse dans la liste :

Je confirme que le fork de nanamkim de custom-err-msg fonctionne avec Rails 5 et avec la configuration locale.

Il vous suffit de démarrer le message de paramètres régionaux avec un signe d'insertion et il ne doit pas afficher le nom d'attribut dans le message.

Un modèle défini comme:

class Item < ApplicationRecord
  validates :name, presence: true
end

avec les éléments suivants en.yml:

en:
  activerecord:
    errors:
      models:
        item:
          attributes:
            name:
              blank: "^You can't create an item without a name."

item.errors.full_messages Affichera:

You can't create an item without a name

au lieu de l'habituel Name You can't create an item without a name

Rystraum
la source
11

Je recommande d'installer le joyau custom_error_message (ou en tant que plugin ) écrit à l'origine par David Easley

Il vous permet de faire des choses comme:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Ryan Bigg
la source
J'ai utilisé ce plugin dans le passé avec beaucoup de succès bien qu'il ne semble plus être régulièrement entretenu.
Jared Brown
1
vous pouvez également l'installer comme une gemme pour les rails 3. ajoutez simplement gem "custom_error_message" à votre Gemfile - voir github pour plus de détails
Dorian
Exactement ce dont j'avais besoin
olleicua
3
@DickieBoy Je confirme que la fourche de nanamkim ( github.com/nanamkim/custom-err-msg ) fonctionne avec Rails 5. Cela joue en fait bien avec la réponse acceptée. Je vais écrire cela comme une réponse distincte.
Rystraum
@Rystraum Pour la vie de moi, je ne me souviens pas du cas d'utilisation autour de cela, mais merci pour la réponse! Je m'en souviendrai à l'avenir.
DickieBoy
10

Une solution pourrait être de changer le format d'erreur par défaut i18n:

en:
  errors:
    format: "%{message}"

La valeur par défaut est format: %{attribute} %{message}

cappie013
la source
7

Voici une autre façon:

Si vous utilisez ce modèle:

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

Vous pouvez écrire votre propre message personnalisé comme ceci:

class Thing < ActiveRecord::Base

  validate :custom_validation_method_with_message

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:_, "My custom message")
    end
  end

De cette façon, en raison du trait de soulignement, le message complet devient "Mon message personnalisé", mais l'espace supplémentaire au début est imperceptible. Si vous ne voulez vraiment pas cet espace supplémentaire au début, ajoutez simplement la .lstripméthode.

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message.lstrip %></li>
    <% end %>
  </ul>
<% end %>

La méthode String.lstrip supprimera l'espace supplémentaire créé par ': _' et laissera les autres messages d'erreur inchangés.

Ou encore mieux, utilisez le premier mot de votre message personnalisé comme clé:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:my, "custom message")
    end
  end

Maintenant, le message complet sera "Mon message personnalisé" sans espace supplémentaire.

Si vous voulez que le message complet commence par un mot en majuscule comme "URL ne peut pas être vide", cela ne peut pas être fait. Essayez plutôt d'ajouter un autre mot comme clé:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:the, "URL can't be blank")
    end
  end

Maintenant, le message complet sera "L'URL ne peut pas être vide"

Cruz Nunez
la source
ooo, vous pouvez même faire errors.add(:_, 'foobar')et obtenir 'foobar' comme message
xxjjnn
6

Faites-le normalement:

validates_presence_of :email, :message => "Email is required."

Mais affichez-le comme ceci à la place

<% if @user.errors.any? %>
  <% @user.errors.messages.each do |message| %>
    <div class="message"><%= message.last.last.html_safe %></div>
  <% end %>
<% end %>

Retour

"Email is required."

La méthode de localisation est certainement la façon "appropriée" de le faire, mais si vous faites un petit projet non global et que vous voulez simplement aller vite - c'est certainement plus facile que le saut de fichier.

Je l'aime pour la possibilité de mettre le nom du champ ailleurs que au début de la chaîne:

validates_uniqueness_of :email, :message => "There is already an account with that email."
brittohalloran
la source
2

Si vous voulez tous les lister dans une belle liste mais sans utiliser le nom cruddy non humain friendly, vous pouvez le faire ...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
Adam
la source
1

À votre avis

object.errors.each do |attr,msg|
  if msg.is_a? String
    if attr == :base
      content_tag :li, msg
    elsif msg[0] == "^"
      content_tag :li, msg[1..-1]
    else
      content_tag :li, "#{object.class.human_attribute_name(attr)} #{msg}"
    end
  end
end

Lorsque vous souhaitez remplacer le message d'erreur sans le nom d'attribut, ajoutez simplement le message avec ^ comme ceci:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
luckyruby
la source
ne fonctionne pas avec les rails 5.1 / ruby ​​2.4? obtenir le nom du modèle dans cette portée
Ben
@Ben fonctionne pour moi sur Rails 5.1.2, Ruby 2.4.1p111. Pouvez-vous partager votre code?
luckyruby
je suppose que je devais regarder plus loin, vous pouvez vérifier le code et sa réponse là stackoverflow.com/q/45128434/102133
Ben
0

J'ai essayé de suivre, j'ai travaillé pour moi :)

1 job.rb

class Job < ApplicationRecord
    validates :description, presence: true
    validates :title, 
              :presence => true, 
              :length => { :minimum => 5, :message => "Must be at least 5 characters"}
end

2 jobs_controller.rb

def create
      @job = Job.create(job_params)
      if @job.valid?
        redirect_to jobs_path
      else
        render new_job_path
      end     
    end

3 _form.html.erb

<%= form_for @job do |f| %>
  <% if @job.errors.any? %>
    <h2>Errors</h2>
    <ul>
      <% @job.errors.full_messages.each do |message|%>
        <li><%= message %></li>
      <% end %>  
    </ul>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :description %>
    <%= f.text_area :description, size: '60x6' %>

  </div>
  <div>
    <%= f.submit %>
  </div>
<% end %> 
Aigul
la source
0

Voici mon code qui peut vous être utile au cas où vous en auriez encore besoin: Mon modèle:

validates :director, acceptance: {message: "^Please confirm that you are a director of the company."}, on: :create, if: :is_director?

Ensuite, j'ai créé un assistant pour afficher les messages:

module ErrorHelper
  def error_messages!
    return "" unless error_messages?
    messages = resource.errors.full_messages.map { |msg|
       if msg.present? && !msg.index("^").nil?
         content_tag(:p, msg.slice((msg.index("^")+1)..-1))
       else
         content_tag(:p, msg)
       end
    }.join

    html = <<-HTML
      <div class="general-error alert show">
        #{messages}
      </div>
    HTML

    html.html_safe
  end

  def error_messages?
    !resource.errors.empty?
  end
end
Oleksii Danylevskyi
la source
0

Une approche unique dont je n'ai vu personne parler!

La seule façon dont j'ai pu obtenir toute la personnalisation que je voulais était d'utiliser un after_validationrappel pour me permettre de manipuler le message d'erreur.

  1. Autorisez la création du message de validation comme d'habitude, vous n'avez pas besoin d'essayer de le modifier dans l'aide à la validation.

  2. créer un after_validationrappel qui remplacera ce message de validation dans le back-end avant qu'il ne parvienne à la vue.

  3. Dans la after_validationméthode, vous pouvez faire tout ce que vous voulez avec le message de validation, comme une chaîne normale! Vous pouvez même utiliser des valeurs dynamiques et les insérer dans le message de validation.


#this could be any validation
validates_presence_of :song_rep_xyz, :message => "whatever you want - who cares - we will replace you later"

after_validation :replace_validation_message

def replace_validation_message
    custom_value = #any value you would like
    errors.messages[:name_of_the_attribute] = ["^This is the replacement message where 
    you can now add your own dynamic values!!! #{custom_value}"]
end

La méthode after_validation aura une portée bien plus grande que l'aide à la validation des rails intégrée, vous pourrez donc accéder à l'objet que vous validez comme vous essayez de le faire avec object.file_name. Ce qui ne fonctionne pas dans l'assistant de validation où vous essayez de l'appeler.

Remarque: nous utilisons le ^pour se débarrasser du nom d'attribut au début de la validation comme @Rystraum l'a souligné en référençant ce joyau

Sami Birnbaum
la source
0

La réponse de graywh est la meilleure si elle est réellement différente dans l'affichage du nom du champ. Dans le cas d'un nom de champ dynamique (basé sur d'autres champs à afficher), je ferais quelque chose comme ça

<% object.errors.each do |attr, msg| %>
<li>
  <% case attr.to_sym %>
  <% when :song_rep_xyz %>
    <%= #display error how you want here %>
  <% else %>
    <%= object.errors.full_message(attr, msg) %>
  <% end %>
</li>
<% end %>

la méthode full_message sur l'autre est ce que les rails utilisent à l'intérieur de la méthode full_messages, donc elle donnera les erreurs Rails normales pour les autres cas (Rails 3.2 et plus)

An Nguyen Dang
la source