rails - Conception - Manipulation - devise_error_messages

125

dans ma page d'édition utilisateur, il y a une ligne comme suit:

<%= devise_error_messages! %>

Le problème est que cela ne génère pas d'erreurs comme le fait le reste de l'application:

<% flash.each do |key, value| %>
    <div class="flash <%= key %>"><%= value %></div>
<% end %>

Ma question est la suivante: comment faire pour que le message d'erreur du périphérique fonctionne comme les autres utilisateurs de flash.each?

Merci.

Un apprenti
la source
1
Veuillez noter que Devise utilise déjà le flash comme le fait le reste de l'application. devise_error_messages ne concerne pas les messages flash (informations de la dernière page), mais plutôt les erreurs de validation d'ActiveRecord Validation guides.rubyonrails.org/v2.3.11/…
Christopher Oezbek

Réponses:

135

J'essaye de comprendre cela moi-même. Je viens de trouver ce problème connecté sur Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

Jose dit que cette devise_error_messsages!méthode n'est qu'un stub (bien qu'elle contienne une implémentation) et que nous sommes censés la remplacer / la remplacer. Cela aurait été bien si cela avait été signalé quelque part dans le wiki, c'est pourquoi je suppose qu'il y a quelques personnes comme nous qui ont deviné.

Je vais donc essayer de rouvrir le module et de redéfinir la méthode, en remplaçant efficacement l'implémentation par défaut. Je vous ferai savoir comment ça se passe.

Mettre à jour

Oui, ça marche. Je l'ai créé app/helpers/devise_helper.rbet écrasé comme ceci:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

Sachant cela, je peux modifier la méthode pour afficher les messages d'erreur comme je le souhaite.

Pour vous aider à résoudre votre problème d'origine: voici l'original devise_helper.rbsur Github . Jetez un œil à la façon dont les messages d'erreur sont parcourus:

messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join

Cela devrait vous aider à démarrer. :)

Une autre mise à jour

L' resourceobjet est en fait le modèle utilisé par le dispositif (voir la figure).

resource.class         #=> User
resource.errors.class  #=> ActiveModel::Error

Il semble également être défini dans une portée plus élevée (provenant probablement du contrôleur), de sorte qu'il peut être consulté dans une variété d'endroits.

N'importe où dans votre assistant

module DeviseHelper
  def devise_error_messages1!
    resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
  end

  def devise_error_messages2!
    resource.errors.full_messages.map { |msg| content_tag(:p, msg) }.join
  end
end

Ta vue

<div><%= resource.errors.inspect %></div>
John
la source
J'ai juste essayé ça mais ça ne marche pas. Le but est d'obtenir l'erreur à afficher ici: <% flash.each do | key, value | %>
AnApprentice
@ColdTree non, le but est que cela fonctionne comme les messages flash. Être capable de contrôler le balisage est une bonne solution.
Benjamin Atkin
... Je ne pense pas que cela réponde à la question bien que ce soit un bon travail de recherche.
deivid
37

La solution ci-dessous fonctionne avec le dernier modèle à partir de maintenant (4.1.1) et Rails 4.2.6. Mais c'est si simple que je ne vois pas la raison pour laquelle cela ne fonctionnerait pas dans 10 ans;)

Si vous souhaitez recycler vos messages d'erreur et leur donner la même apparence dans votre application, je recommanderais quelque chose comme ceci (comme j'ai appris avec Michael Hartl tut):

Créer partiel pour les messages d'erreur: layouts/_error_messages.html.erb Mettez à l'intérieur du code suivant (ici, j'utilise des classes bootstrap 3):

<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger alert-dismissable">
      <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
      <p><strong>This form contains <%= pluralize(object.errors.count, 'error') %>.</strong></p>
      <ul>
        <% object.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  </div>
<% end %>

Maintenant, vous avez quelque chose de recyclable et vous pouvez l'utiliser partout. Au lieu d'un appareil standard:

<%= devise_error_messages! %>

Appelez-le dans votre formulaire comme ceci:

<%= render 'layouts/error_messages', object: resource %>

Vous pouvez le mettre sous n'importe quelle forme. Au lieu de passer la ressource de conception, vous pouvez passer la variable de votre formulaire comme ceci:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>
Lukasz Muzyka
la source
1
Probablement la réponse la meilleure et la plus intuitive qui soit.
Victor
2
Solution fraîche. pluralize (object.errors.count, 'errors' doit être changé en pluralize (object.errors.count, 'error' cependant
mizurnix
1
@LukaszMuzyka dans cette solution .. dois-je supprimer: validable de user.rb .. ???
Vishal
1
@Vishal - non. La solution ci-dessus utilise simplement un code HTML différent pour afficher les messages, cela ne change aucun mécanisme de Devise
Lukasz Muzyka
1
@Vishal lorsque vous utilisez devise, il effectue déjà les validations que vous avez mentionnées sans code supplémentaire. La solution ci-dessus consiste uniquement à remplacer le comportement par défaut du dispositif. Il faut d'abord avoir un appareil qui fonctionne. Êtes-vous sûr d'avoir suivi les instructions pour intégrer la conception à votre projet?
Lukasz Muzyka
22

Je sais que cela fait un moment que cette question a été publiée, mais je voulais juste commenter ce que j'ai trouvé. Les deux personnes qui ont déjà répondu m'ont été d'une aide précieuse et je voulais juste contribuer.

Vous verrez tout au long de Devise que des appels utilisent render_with_scope. Je crois que c'est une méthode définie par devise et applique essentiellement la portée actuelle à la vue suivante rendue.

Pourquoi est-ce pertinent? Devise contient vos erreurs dans resource.errors( pas @resource.errors ). Devise fonctionne bien si vous souhaitez l'utiliser hors de la boîte, pour ainsi dire.

Des problèmes avec ces erreurs surviennent si vous commencez à modifier votre comportement de gestion des utilisateurs. En ajoutant un redirect_toou render(au lieu de render_with_scope) là où Devise n'en avait pas auparavant, vous jetez essentiellement les messages d'erreur. Cela rend Devise hostile à la modification, à mon avis.

Ma solution est la suivante

# In application.html.erb
<% flash.each do |name, msg| %>

  # New code (allow for flash elements to be arrays)
  <% if msg.class == Array %>
    <% msg.each do |message| %>
      <%= content_tag :div, message, :id => "flash_#{name}" %>
    <% end %>
  <% else %>

    # old code
    <%= content_tag :div, msg, :id => "flash_#{name}" %>

  <% end %> #don't forget the extra end
<% end %>

et

# Wherever you want Devise's error messages to be handled like 
# your other error messages
# (in my case, registrations_controller.rb, a custom controller)
flash[:notice] = flash[:notice].to_a.concat resource.errors.full_messages

Le dernier bloc de code prend les messages d'erreur de Devise sous forme de tableau et les ajoute flash[:notice](sous forme de tableau). Chaque message sera imprimé une ligne à la fois. Si j'ai le temps, je pense que je vais changer la façon dont Devise gère les messages d'erreur pour le faire dans mon application, car il semble beaucoup plus propre d'avoir un système de messages d'erreur au lieu de deux.

Eric Hu
la source
3
Merci beaucoup pour ça, je me cognais la tête contre le mur pour avoir essayé de faire ça.
Lucas
1
C'est maintenant 5 ans plus tard et cette réponse a sauvé mon bacon. Merci beaucoup @ eric-hu.
marcamillion
12

J'ai résolu cela de la même manière que YoyoS, en créant un app/helpers/devise_helper.rbet en le plaçant dedans:

module DeviseHelper

  # Hacky way to translate devise error messages into devise flash error messages
  def devise_error_messages!
    if resource.errors.full_messages.any?
        flash.now[:error] = resource.errors.full_messages.join(' & ')
    end
    return ''
  end
end

Travaillé!

r123454321
la source
11

Je veux juste apporter un nouveau petit morceau ici:

J'ai donc trouvé un moyen plus simple d'obtenir le résultat souhaité par "AnApprentice".

Tout d'abord, si vous souhaitez personnaliser quoi que ce soit dans le plug-in Devise, je vous conseille vivement de copier après le code de "\ Ruby_repertory \ lib \ ruby ​​\ gems \ 1.9.1 \ gems \ devise-version \ app \ controllers | helpers | mailers ... "dans le fichier souhaité dans votre projet.

[Modifier] Ou vous pouvez faire en sorte que votre fichier hérite des fichiers de devise "normaux" ... Comme ... disons ... Vous ne voulez écraser qu'une seule fonction dans le fichier devise / registrations_controller.rb, la première ligne de vos utilisateurs personnalisés le contrôleur des enregistrements serait:

class Users::RegistrationsController < Devise::RegistrationsController

[Edit 7 août 2013] Now Devise fournit même un outil pour générer des contrôleurs: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

Alors ... de toute façon ... j'ai réussi à obtenir ce que "AnApprentice" voulait juste écrire ceci (pour une solution plus propre, voir la grande modification suivante):

#/my_project/app/helpers/devise_helper.rb
module DeviseHelper
   def devise_error_messages!
      return "" if resource.errors.empty?

      return resource.errors
   end
end

Et, à mon avis, les lignes suivantes ont plutôt bien fonctionné:

<% devise_error_messages!.each do |key, value| %>
    <div class="flash <%= key %>"><%= key %> <%= value %></div>
<% end %>

Eh bien ... alors vous pouvez accéder aux erreurs pour un attribut spécifique comme celui-ci:

    #Imagine you want only the first error to show up for the login attribute:
    <%= devise_error_messages![:login].first %> 

Et ... Une petite astuce pour n'avoir qu'une seule erreur (la première à être rattrapée) apparaissant par attribut:

<% if resource.errors.any? %>
  <% saved_key = "" %>
  <% devise_error_messages!.each do |key, value| %>
    <% if key != saved_key %>
        <div class="flash <%= key %>"><%= key %> <%= value %></div>
    <% end %>
    <% saved_key = key %>
  <% end %>
<% end %>

Je sais que cela fait un moment que cette question a été publiée, mais je pense que cela aidera beaucoup d'utilisateurs de la conception :).

Grand montage:

Comme j'aime étendre mon code, le rendre plus propre et le partager avec les autres, j'ai récemment voulu changer les messages de devise_error_messages! afin de l'utiliser dans mes vues et de lui faire afficher l'astuce que j'ai expliquée ci-dessus.

Alors, voici ma méthode:

 def devise_error_messages! 
    html = ""

    return html if resource.errors.empty?

    errors_number = 0 

    html << "<ul class=\"#{resource_name}_errors_list\">"

    saved_key = ""
    resource.errors.each do |key, value|
      if key != saved_key
        html << "<li class=\"#{key} error\"> This #{key} #{value} </li>"
        errors_number += 1
      end
      saved_key = key
    end

    unsolved_errors = pluralize(errors_number, "unsolved error")
    html = "<h2 class=\"#{resource_name}_errors_title\"> You have #{unsolved_errors} </h2>" + html
    html << "</ul>"

    return html.html_safe
 end

Ce n'est pas grave ici, j'ai réutilisé le code que j'ai écrit à mon avis pour n'afficher qu'un seul attribut d'erreur pey, car souvent le premier est le seul pertinent (comme lorsque l'utilisateur oublie un champ obligatoire).

Je compte ces erreurs "uniques" et je crée un titre HTML H2 en utilisant pluralize et en le mettant AVANT la liste des erreurs.

Alors maintenant, je peux utiliser les messages "devise_error_messages!" par défaut et il rend exactement ce que je rendais déjà avant.

Si vous voulez accéder à un message d'erreur spécifique dans votre vue, je recommande maintenant d'utiliser directement "resource.errors [: attribute] .first" ou autre.

Seya, Kulgar.

Kulgar
la source
6

J'utilise Devise dans Rails 3 et votre code flash est à peu près identique à ce que j'ai. Dans mon application, le code fonctionne comme prévu; c'est-à-dire que les messages d'erreur de Devise sont émis avec le reste de mes messages flash:

<% flash.each do |name, msg| %>
  <%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %>
<% end %>

Essayez ce code exact et voyez s'il fait une différence - l'attribut ID différent peut vous aider.

Scott
la source
merci mais cela finit par ne rien montrer. "<% = devise_error_messages!%>" génère une erreur. ce qui précède n'a rien fait? des idées?
AnApprentice
Toutes mes excuses, je viens de voir votre commentaire, pour être honnête, je suis à court d'idées. Je suppose que vous avez consulté la source dans votre navigateur et vérifié le code HTML généré? Juste au cas où quelque chose serait caché par CSS. Utilisez-vous la dernière version de Devise 1.1.3?
Scott
5

J'en suis venu à cela et cela fonctionne jusqu'à présent. Cela ajoute des messages de conception au flash, de sorte qu'il peut être utilisé comme d'habitude. Veuillez considérer que je suis nouveau dans Ruby and Rails ...

class ApplicationController < ActionController::Base
  after_filter :set_devise_flash_messages, :if => :devise_controller?
  ...

  private:

  def set_devise_flash_messages
    if resource.errors.any?
      flash[:error] = flash[:error].to_a.concat resource.errors.full_messages
      flash[:error].uniq!
    end
  end
end

Éditer:

Désolé, je faisais la garde et un comportement indésirable était présent. Depuis after_filterest appelé après le rendu, il ne fonctionne donc pas comme prévu. Si quelqu'un sait appeler une méthode après l'action mais avant le rendu ...

Mais vous pouvez utiliser quelque chose comme ça à la place:

module ApplicationHelper

  # merge the devise messages with the normal flash messages
  def devise_flash
    if controller.devise_controller? && resource.errors.any?
      flash.now[:error] = flash[:error].to_a.concat resource.errors.full_messages
      flash.now[:error].uniq!
    end
  end

end

Dans views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->
ddidier
la source
1
+1 Excellente réponse. Je pense que c'est certainement la solution la plus propre et s'intègre parfaitement dans mon architecture actuelle. La réponse n'est pas si claire cependant - fondamentalement, tout avant la modification doit être ignoré (et supprimé ou supprimé par imo).
zelanix
3

Si vous voulez pouvoir afficher plus d'un flash d'un type donné (: alert,: notice, etc ...) et ne pas perdre votre temps à essayer de modifier le comportement d'un gem, c'est la solution que j'ai utilisée avec Devise. Je suis presque sûr qu'il pourrait être utilisé avec n'importe quelle gemme qui utilise des messages flash.

Première chose à faire, dans votre application_controller.rb, ajoutez ceci:

  # Adds the posibility to have more than one flash of a given type
  def flash_message(type, text)
    flash[type] ||= []
    flash[type] << text
  end

Deuxième chose à faire, afficher vos messages flash avec ceci dans application.html.erb (ou où vous voulez):

   <div class="flashes">
      <% flash.each do |key, messages| %>
        <% messages = Array(messages) unless messages.is_a?(Array) %>
        <% messages.each do |message| %>
        <div class="alert alert-<%= key %>">
          <%= message %>
        </div>
        <% end %>
      <% end %>
    </div>

Troisième chose à faire, chaque fois que vous souhaitez ajouter un message flash dans n'importe quel contrôleur, procédez comme suit:

flash_message(:success, "The user XYZ has been created successfully.")

la source
Mais comment faire en sorte que les messages Devise appellent flash_messages au lieu de conserver un objet d'erreur.
Christopher Oezbek
3

Créez DeviseHelper:

module DeviseHelper
  def devise_error_messages!
    return "" if resource.errors.empty?

    messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg)}.join
    return flash.now[:alert] = messages.html_safe
  end
end

À votre avis, remplacez

<%= devise_error_messages! %>

À:

<% devise_error_messages! %>
BM
la source
1
En fait, vous devriez utiliser: flash.now [: alert]
BM
2

Certes, un peu piraté, mais j'utilise cet assistant (app / helpers / devise_helper.rb) pour récupérer les flashs et les utiliser s'ils sont définis par défaut resource.errors. Ceci est juste basé sur l'aide qui se trouve dans la librairie de conception.

module DeviseHelper

  def devise_error_messages!
    flash_alerts = []
    error_key = 'errors.messages.not_saved'

    if !flash.empty?
      flash_alerts.push(flash[:error]) if flash[:error]
      flash_alerts.push(flash[:alert]) if flash[:alert]
      flash_alerts.push(flash[:notice]) if flash[:notice]
      error_key = 'devise.failure.invalid'
    end

    return "" if resource.errors.empty? && flash_alerts.empty?
    errors = resource.errors.empty? ? flash_alerts : resource.errors.full_messages

    messages = errors.map { |msg| content_tag(:li, msg) }.join
    sentence = I18n.t(error_key, :count    => errors.count,
                                 :resource => resource.class.model_name.human.downcase)

    html = <<-HTML
    <div id="error_explanation">
      <h2>#{sentence}</h2>
      <ul>#{messages}</ul>
    </div>
    HTML

    html.html_safe
  end

end
typeoneerror
la source
2

Si vous cherchez à tirer parti de devise_error_messages, vous pouvez le faire en ajoutant à resource.errors

Si vous deviez dépasser le contrôleur d'enregistrement, cela pourrait ressembler à

def create
  if validation_or_other_check_passes
    super
  else
    build_resource
    clean_up_passwords(resource)
    resource.errors.add(:notice, "The check failed.")
    render :new 
Douglas Drouillard
la source
2

Moyen très simple d'afficher un message d'erreur pour chaque champ

<%= resource.errors.messages[:email].join(" ") %>

mettez pour chaque champ avec le nom de champ entre crochets sous chaque ligne où vous voulez afficher un message d'erreur en ligne.

SSR
la source
1

Pour afficher l'erreur de votre appareil à partir de votre contrôleur avec seulement la première erreur à apparaître.

flash[:error] = @resource.errors.full_messages.first
pseudo
la source
1

Juste pour ajouter à la réponse d'Eric Hu ci-dessus où toutes les instructions If sont utilisées, faites plutôt quelque chose comme ça à la place.

# Controller
flash.now[:error] = flash[:error].to_a.concat(resource.errors.full_messages)

# View
<% flash.each do |name, msg| %>
 <% Array(msg).uniq.each do |message| %>
  <%= message %>
 <% end %>
<% end %>
ChuckJHardy
la source
1

je fais simplement cela, a fonctionné pour moi: dans app / helpers / , je crée un fichier devise_helper.rb

  module DeviseHelper

  def devise_error_messages_for(resource)
    return "" if resource.errors.empty?

    messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
    sentence = I18n.t("errors.messages.not_saved",
                      count: resource.errors.count,
                      resource: resource.class.model_name.human.downcase)

    html = <<-HTML
    <div id="error_explanation">
      <h2>#{sentence}</h2>
      <ul>#{messages}</ul>
    </div>
    HTML

    html.html_safe
  end
end

dans tous les fichiers de vue que je change

<%= devise_error_messages! %>

pour:

<%= devise_error_messages_for(#your object in your formular)%>

pour moi, il fait à mon avis modifier et nouvel utilisateur:

  <%=form_for resource, as: @user, url: user_path(@user),...
      <%= devise_error_messages_for(@user) %>

j'espère que cela vous aidera;)

dev.guillaumem59
la source
Je ne comprends vraiment pas comment cela fait quoi que ce soit? c'est un comportement standard? C'est juste une autre façon de faire <%= devise_error_messages! %>et ne répond pas à la question. La question demande comment appliquer le flash à chaque message.
Mark
0
  1. Supprimez les messages "devise_error_messages!" à partir du modèle "application / vues / utilisateurs / mots de passe / nouveau".
  2. Créez un contrôleur personnalisé pour votre utilisateur (app / controllers / users / passwords_controller.rb) et dans un filtre après filtre, ajoutez un tableau flash d'erreurs:
class Users::PasswordsController < Devise::PasswordsController
  after_filter :flash_errors

  def flash_errors
    unless resource.errors.empty?
      flash[:error] = resource.errors.full_messages.join(", ")
    end
  end
end
Gacha
la source
0

J'aime le faire comme dans l'autre contrôleur Devise avec cette astuce.

<% if flash.count > 0 %>
  <div id="error_explanation">
    <h2>Errors prevented you from logging in</h2>
      <ul>
        <% flash.each do |name, msg| %>
        <li>
          <%= content_tag :div, msg, id: "flash_#{name}" %>
        </li>
       <% end %>
     </ul>
   </div>
<% end %>
botbot
la source
0

Pour que materialisecss affiche les messages d'erreur de conception sous forme de toast, j'ai ajouté ce code dans app / helpers / devise_helper.rb

module DeviseHelper
  def devise_error_messages!

    messages = resource.errors.full_messages.map { |msg|
      String.new(" M.toast({html: '" + msg + "' }); ".html_safe )
    }.join

    messages = ("<script>" + messages + "</script>").html_safe
  end 
end

Je suis sûr que ce serait la façon la plus propre de l'écrire mais ça se réveille parfaitement

Grégoire Mulliez
la source
0

DeviseHelper#devise_error_messages! est obsolète et sera supprimé dans la prochaine version majeure.

Devise utilise désormais un sous partiel devise/shared/error_messagespour afficher les messages d'erreur par défaut et les rendre plus faciles à personnaliser. Mettez à jour vos vues en modifiant les appels de:

      <%= devise_error_messages! %>

à:

      <%= render "devise/shared/error_messages", resource: resource %>
Muhammad Nasir Shamshad
la source
-1

Je viens de créer un app/helpers/devise_helper.rbcomme John mais j'ai remplacé la méthode comme ça:

module DeviseHelper
  def devise_error_messages!
    flash[:error] = resource.errors.full_messages.join('<br />')
    return ''
  end
end

Avec cela, je n'ai rien d'autre à modifier. Est-ce une mauvaise idée? Je suis nouveau sur les rails, n'hésitez pas à me corriger. Merci.

YoyoS
la source
Cela ne fonctionnera pas comme vous le souhaitez, le message flash contient maintenant une balise HTML <br>. Normalement, vous ne mettez de la chaîne que dans votre message flash.
AZ.
Peut-être, mais la nouvelle ligne fonctionne toujours. Proposez une autre solution si vous n'aimez pas celle-ci.
YoyoS
-2

Je viens de déclarer devise_error_messages! comme une aide vide. Et manuellement récupéré et géré les erreurs dans un _errors partiel général pour mon application. Cela semblait être la solution la plus simple et je n'ai pas besoin de parcourir tous les fichiers de l'appareil et de supprimer l'appel au gestionnaire d'erreurs.

Harry Moreno
la source