rieles - Devise - Manipulación - devise_error_messages


125

en mi página de edición de usuario, hay una línea de la siguiente manera:

<%= devise_error_messages! %>

El problema es que esto no genera errores de la manera estándar que hace el resto de la aplicación:

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

Mi pregunta es, ¿cómo hago para que el mensaje de error de diseño funcione como los demás que usan el flash cada uno?

Gracias.


1
Tenga en cuenta que Devise ya está utilizando el flash como lo está haciendo el resto de la aplicación. devise_error_messages no se trata de mensajes flash (información de la última página), sino de errores de validación de ActiveRecord Validation guides.rubyonrails.org/v2.3.11/…
Christopher Oezbek

Respuestas:


135

Estoy tratando de resolver esto yo mismo. Acabo de encontrar este problema registrado en Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

José dice que el devise_error_messsages!método es solo un trozo (aunque contiene implementación) y que se supone que debemos anularlo / reemplazarlo. Hubiera sido bueno que esto se señalara en algún lugar de la wiki, por lo que supongo que hay algunas personas como nosotros que han estado adivinando.

Así que voy a intentar volver a abrir el módulo y redefinir el método, anulando efectivamente la implementación predeterminada. Te diré cómo va.

Actualizar

Sí, eso funciona. Lo creé app/helpers/devise_helper.rby lo anulé así:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

Entonces, sabiendo esto, puedo modificar el método para mostrar los mensajes de error de la manera que quiero.

Para ayudarlo a resolver su problema original: Aquí está el original devise_helper.rben Github . Eche un vistazo a cómo se atraviesan los mensajes de error:

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

Eso debería ayudarte a comenzar. :)

Otra actualización

El resourceobjeto es en realidad el modelo que está utilizando el dispositivo (figura).

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

También parece estar definido en un alcance más alto (probablemente proveniente del controlador), por lo que se puede acceder en una variedad de lugares.

En cualquier lugar de tu ayudante

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

Su vista

<div><%= resource.errors.inspect %></div>

Acabo de probar esto pero eso no funciona. El objetivo es obtener el error para mostrar aquí: <% flash.each do | key, value | %>
AnApprentice

@ColdTree no, el objetivo es que funcione como los mensajes flash. Ser capaz de controlar el marcado es una buena solución.
Benjamin Atkin el

... No creo que esto responda la pregunta, aunque es un buen trabajo de investigación.
deivid

37

La siguiente solución funciona con el último dispositivo hasta ahora (4.1.1) y Rails 4.2.6. Pero es tan simple que no veo la razón por la cual no funcionaría dentro de 10 años;)

Si desea reciclar sus mensajes de error y hacer que se vean de la misma manera en su aplicación, recomendaría algo como esto (como aprendí con Michael Hartl tut):

Crear parciales para mensajes de error: layouts/_error_messages.html.erb poner dentro del siguiente código (aquí uso algunas clases de 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 %>

Ahora tiene algo reciclable y puede usarlo en todos los ámbitos. En lugar del dispositivo estándar:

<%= devise_error_messages! %>

Llámalo en tu formulario así:

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

Puedes ponerlo en cualquier forma. En lugar de pasar recursos de diseño, puede pasar variables de su formulario de esta manera:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>

1
Probablemente la mejor y más intuitiva respuesta de todas.
Victor

2
Solución genial pluralize (object.errors.count, 'errores' deben cambiarse a pluralize (object.errors.count, 'error' aunque
mizurnix

1
@LukaszMuzyka en esta solución ... ¿necesito eliminar: validable de user.rb ... ???
Vishal

1
@Vishal - no. La solución anterior simplemente usa HTML diferente para mostrar los mensajes, no cambia la mecánica de Devise
Lukasz Muzyka

1
@Vishal cuando está usando el dispositivo ya está haciendo las validaciones que ha mencionado sin ningún código adicional. La solución anterior es solo anular el comportamiento de diseño predeterminado. Tienes que haber ideado trabajar en primer lugar. ¿Está seguro si ha seguido las instrucciones para integrar el diseño con su proyecto?
Lukasz Muzyka

22

Sé que ha pasado un tiempo desde que se publicó esta pregunta, pero solo quería comentar lo que he encontrado. Las dos personas que ya respondieron han sido de gran ayuda para mí y solo quería contribuir.

Verá en todo el dispositivo que hay llamadas usando render_with_scope. Creo que este es un método definido por idear y básicamente aplica el alcance actual a la siguiente vista representada.

¿Por qué es esto relevante? El dispositivo contiene sus errores dentro resource.errors( no @resource.errors ). Idear funciona bien si quieres usarlo de fábrica, por así decirlo.

Surgen problemas con estos errores si comienza a cambiar su comportamiento de administración de usuarios. Al agregar un redirect_too render(en lugar de render_with_scope) donde Devise no tenía uno anteriormente, básicamente está arrojando los mensajes de error. Esto hace que Devise sea hostil a la modificación, en mi opinión.

Mi solución es esta

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

y

# 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

El último bloque de código toma los mensajes de error de Devise como una matriz y los agrega flash[:notice](como una matriz). Cada mensaje se imprimirá una línea a la vez. Si tengo tiempo, creo que voy a cambiar la forma en que Devise maneja los mensajes de error para hacer esto en toda mi aplicación, ya que parece mucho más limpio tener un sistema de mensajes de error en lugar de dos.


3
Muchas gracias por eso, me estaba golpeando la cabeza contra la pared por intentar hacer eso.
Lucas

1
Han pasado 5 años y esta respuesta me salvó el tocino. Muchas gracias @ eric-hu.
marcamillion

12

Resolví esto de manera similar a YoyoS, creando app/helpers/devise_helper.rby colocando esto en él:

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

¡Trabajó!


11

Solo quiero traer una pequeña pieza nueva aquí:

Así que encontré una manera más fácil de obtener el resultado que "AnApprentice" quería.

En primer lugar, si desea personalizar algo dentro del complemento Devise, le recomiendo que copie el código de "\ Ruby_repertory \ lib \ ruby ​​\ gems \ 1.9.1 \ gems \ devise-version \ app \ controllers | helpers | mailers ... "al archivo que desea en su proyecto.

[Editar] O puede hacer que su archivo herede de los archivos de diseño "normales" ... Como ... diga ... Desea sobrescribir solo una función dentro del dispositivo / registrations_controller.rb, la primera línea de sus Usuarios personalizados el controlador de registros sería:

class Users::RegistrationsController < Devise::RegistrationsController

[Editar 7 de agosto de 2013] Ahora, Devise incluso proporciona una herramienta para generar controladores: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

Entonces ... de todos modos ... logré obtener lo que "AnApprentice" quería simplemente escribiendo esto (para una solución más limpia, vea la siguiente gran edición):

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

      return resource.errors
   end
end

Y, en mi opinión, las siguientes líneas funcionaron bastante bien:

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

Bueno ... entonces puedes acceder a errores para un atributo específico como este:

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

Y ... Un pequeño truco para que solo aparezca un error (el primero en ser atrapado) por atributo:

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

Sé que ha pasado un tiempo desde que se publicó esta pregunta, pero creo que ayudará a muchos usuarios de diseño :).

Edición grande:

Como me encanta extender mi código, hacerlo más limpio y compartirlo con otros, ¡recientemente quise cambiar los devise_error_messages! para usarlo en mis vistas y hacer que muestre el truco que expliqué anteriormente.

Entonces, aquí está mi método:

 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

No es gran cosa aquí, reutilicé el código que escribí en mi vista para mostrar solo un atributo de error de pey, porque a menudo el primero es el único relevante (como cuando el usuario olvida un campo obligatorio).

Estoy contando esos errores "únicos" y estoy haciendo un título HTML H2 usando pluralizar y poniéndolo ANTES de la lista de errores.

Así que ahora, puedo usar "devise_error_messages". como el predeterminado y muestra exactamente lo que ya estaba renderizando antes.

Si desea acceder a un mensaje de error específico en su vista, ahora le recomiendo usar directamente "resource.errors [: attribute] .first" o lo que sea.

Seya, Kulgar.


6

Estoy usando Devise en Rails 3 y su código flash es prácticamente idéntico al que tengo. En mi aplicación, el código funciona como se esperaba; es decir, se generan mensajes de error con el resto de mis mensajes flash:

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

Pruebe este código exacto y vea si hace alguna diferencia: los diferentes atributos de ID pueden ayudar.


gracias pero eso termina mostrando nada. "<% = devise_error_messages!%>" genera un error. lo anterior no hizo nada? ideas?
AnApprentice el

Disculpas: acabo de ver tu comentario. Para ser sincero, me estoy quedando sin ideas. ¿Supongo que has visto la fuente en tu navegador y has verificado el HTML que se genera? Por si acaso CSS oculta algo. ¿Está utilizando la última versión de Devise 1.1.3?
Scott, el

5

Llegué a esto y está funcionando hasta ahora. Eso agrega mensajes de diseño al flash, por lo que puede usarse como de costumbre. Tenga en cuenta que soy nuevo en 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

Editar:

Lo siento, estaba haciendo guardia y había algún comportamiento no deseado. Como after_filterse llama después del renderizado, no funciona como se esperaba. Si alguien sabe cómo llamar a un método después de la acción pero antes de la representación ...

Pero puedes usar algo así en su lugar:

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

En views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->

1
+1 Gran respuesta. Creo que esta es definitivamente la solución más limpia y se adapta muy bien a mi arquitectura actual. Sin embargo, la respuesta no es tan clara: básicamente, todo antes de la edición debe ignorarse (y eliminarse o eliminarse a través de imo).
zelanix

3

Si desea poder mostrar más de un destello de un tipo determinado (: alerta,: aviso, etc.) y no perder el tiempo tratando de modificar el comportamiento de una gema, esta es la solución que utilicé con Devise. Estoy bastante seguro de que podría usarse con cualquier gema que use mensajes flash.

Lo primero que debe hacer, en su application_controller.rb, agregue esto:

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

Lo segundo que debe hacer, mostrar sus mensajes flash con esto en application.html.erb (o donde desee):

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

Tercero, cuando desee agregar un mensaje flash en cualquier controlador, haga lo siguiente:

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

Pero, ¿cómo hacer que los mensajes Devise llamen flash_messages en lugar de mantener un objeto de error?
Christopher Oezbek

3

Crear 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

En su opinión, sustituto

<%= devise_error_messages! %>

A:

<% devise_error_messages! %>

1
En realidad, deberías usar: flash.now [: alert]
BM

2

Es cierto que es un poco hacky, pero estoy usando este ayudante (app / helpers / devise_helper.rb) para capturar flashes y usarlos si están configurados y luego predeterminados resource.errors. Esto se basa solo en el ayudante que está en el dispositivo lib.

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

2

Si está buscando aprovechar devise_error_messages, puede hacerlo agregando resource.errors

Si tuviera que anular el controlador de registro, podría parecer

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 

2

Manera muy fácil de mostrar mensajes de error para cada campo

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

coloque para cada campo con el nombre del campo entre corchetes debajo de cada línea donde desea mostrar el mensaje de error en línea.


1

Para mostrar su error de diseño desde su controlador con solo el primer error que aparece.

flash[:error] = @resource.errors.full_messages.first

1

Solo para agregar a Eric Hu la respuesta anterior donde se usan todas las declaraciones If, en su lugar, haga algo como esto.

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

1

simplemente hago esto, funcionó para mí: en app / helpers / , creo un archivo 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

en todos los archivos de vista que cambio

<%= devise_error_messages! %>

para:

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

para mí hace que en mi vista edite y nuevo usuario:

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

Espero que te ayude ;)


¿Realmente no entiendo cómo esto hace algo? Este es el comportamiento estándar? Es solo otra forma de hacerlo <%= devise_error_messages! %>y no responde la pregunta. La pregunta pregunta cómo aplicar flash a cada mensaje.
Mark

0
  1. Elimine los "mensajes de error_devise". desde la plantilla "app / views / users / passwords / new".
  2. Cree un controlador personalizado para su usuario (app / controllers / users / passwords_controller.rb) y en un filtro posterior agregue errores flash array:
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

0

Me gusta hacerlo tal como se hace en el otro controlador Devise con este truco.

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

0

Para que materialisecss muestre mensajes de error de dispositivo como tostadas, agregué este código en 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

Estoy seguro de que sería la forma más limpia de escribirlo, pero está funcionando perfectamente


0

DeviseHelper#devise_error_messages! está en desuso y se eliminará en la próxima versión principal.

Devise ahora usa un under parcial devise/shared/error_messagespara mostrar mensajes de error por defecto y hacerlos más fáciles de personalizar. Actualice sus vistas cambiando llamadas desde:

      <%= devise_error_messages! %>

a:

      <%= render "devise/shared/error_messages", resource: resource %>

-1

Acabo de crear un me app/helpers/devise_helper.rbgusta John, pero anulé el método así:

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

Con esto no tengo que modificar nada más. Es una mala idea ? Soy nuevo en los rieles, no dudes en corregirme. Gracias.


Esto no funcionará como se desea, el mensaje flash ahora contiene la etiqueta html <br>. Normalmente solo pones cadena en tu mensaje flash.
AZ.

Quizás, pero la nueva línea aún funciona. Proponga otra solución si no le gusta esta.
YoyoS

-2

¡Acabo de declarar devise_error_messages! como un ayudante vacío Y obtuve y manejé manualmente los errores en un error general parcial para mi aplicación. Parecía la solución más simple y no tengo que revisar todos los archivos del dispositivo y eliminar la llamada al controlador de errores.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.