trilhos - Devise - Manipulação - devise_error_messages

125

na minha página de edição do usuário, há uma linha da seguinte maneira:

<%= devise_error_messages! %>

O problema é que isso não gera erros da maneira padrão que o restante do aplicativo:

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

Minha pergunta é: como faço para que a mensagem de erro do dispositivo funcione como as outras que usam o flash.each?

Obrigado.

Um aprendiz
fonte
1
Observe que o Devise já está usando o flash, como o restante do aplicativo. devise_error_messages não é sobre mensagens flash (informação a partir da última página), mas sim de validação erros de ActiveRecord Validação guides.rubyonrails.org/v2.3.11/...
Christopher Oezbek

Respostas:

135

Estou tentando descobrir isso sozinho. Acabei de encontrar este problema registrado no Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

Jose está dizendo que o devise_error_messsages!método é apenas um esboço (embora contenha implementação) e que devemos substituí-lo / substituí-lo. Teria sido bom se isso fosse apontado em algum lugar do wiki, e é por isso que acho que existem algumas pessoas como nós que têm adivinhado.

Então, vou tentar reabrir o módulo e redefinir o método, substituindo efetivamente a implementação padrão. Eu vou deixar você saber como vai.

Atualizar

Sim, isso funciona. Eu criei app/helpers/devise_helper.rbe substituí-lo da seguinte forma:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

Então, sabendo disso, posso modificar o método para exibir as mensagens de erro da maneira que desejo.

Para ajudá-lo a resolver seu problema original: Aqui está o original devise_helper.rbno Github . Veja como as mensagens de erro estão sendo percorridas:

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

Isso deve ajudar você a começar. :)

Outra atualização

O resourceobjeto é, na verdade, o modelo que está sendo usado pelo dispositivo (veja a figura).

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

Também parece estar definido em um escopo mais alto (provavelmente vindo do controlador), para que possa ser acessado em vários locais.

Em qualquer lugar do seu auxiliar

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

Sua visão

<div><%= resource.errors.inspect %></div>
John
fonte
Eu apenas tentei isso, mas isso não funciona. O objetivo é obter o erro aqui: <% flash.each do | key, value | %>
AnApprentice
@ ColdTree não, o objetivo é que ele funcione como as mensagens em flash. Ser capaz de controlar a marcação é uma boa solução.
Benjamin Atkin
... Acho que isso não responde à pergunta, embora seja um bom trabalho de pesquisa.
26513 deivid deivid
37

A solução abaixo funciona com a versão mais recente a partir de agora (4.1.1) e o Rails 4.2.6. Mas é tão simples que não vejo a razão pela qual não funcionaria daqui a 10 anos;)

Se você deseja reciclar suas mensagens de erro e fazer com que elas tenham a mesma aparência em seu aplicativo, eu recomendaria algo assim (como aprendi com o tutor de Michael Hartl):

Crie parcial para mensagens de erro: layouts/_error_messages.html.erb Coloque o seguinte código (aqui eu uso algumas classes de autoinicialização 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 %>

Agora você tem algo reciclável e pode usá-lo em geral. Em vez do modelo padrão:

<%= devise_error_messages! %>

Chame-o em seu formulário como este:

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

Você pode colocá-lo de qualquer forma. Em vez de passar o recurso do dispositivo, você pode passar variáveis ​​do seu formulário da seguinte maneira:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>
Lukasz Muzyka
fonte
1
Provavelmente a melhor e mais intuitiva resposta de sempre.
213 Victor Victor
2
Solução legal. pluralize (object.errors.count, 'erros' devem ser alteradas para pluralizar (object.errors.count, 'error' embora
mizurnix
1
@LukaszMuzyka nesta solução .. preciso remover: validatable do user.rb .. ???
Vishal
1
@Vishal - não. Acima de solução, simplesmente usa HTML diferente para exibir as mensagens não muda quaisquer mecânica do Devise
Lukasz Muzyka
1
O @Vishal, quando você está usando o devise, já está fazendo as validações que você mencionou sem nenhum código extra. A solução acima é apenas para substituir o comportamento do dispositivo padrão. Você tem que ter um pensamento trabalhando em primeiro lugar. Tem certeza de que seguiu as instruções para integrar o dispositivo ao seu projeto?
Lukasz Muzyka
22

Eu sei que já faz um tempo desde que essa pergunta foi publicada, mas eu só queria comentar o que encontrei. As duas pessoas que já responderam foram uma tremenda ajuda para mim e eu só queria contribuir.

Você verá no Devise que existem chamadas usando render_with_scope. Eu acredito que este é um método definido pelo dispositivo e basicamente aplica o escopo atual à próxima visualização renderizada.

Por que isso é relevante? O dispositivo contém seus erros dentro resource.errors( não @resource.errors ). O Devise funciona bem se você quiser usá-lo imediatamente, por assim dizer.

Problemas com esses erros surgem se você começar a alterar seu comportamento de gerenciamento de usuários. Ao adicionar um redirect_toou render(em vez de render_with_scope) onde o Devise anteriormente não tinha um, você basicamente estará descartando as mensagens de erro. Isso torna o Devise hostil à modificação, na minha opinião.

Minha solução é essa

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

e

# 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

O último bloco de código pega as mensagens de erro do Devise como uma matriz e as anexa a flash[:notice](como uma matriz). Cada mensagem será impressa uma linha de cada vez. Se eu tiver tempo, acho que vou mudar a maneira como o Devise lida com as mensagens de erro para fazer isso em todo o meu aplicativo, pois parece muito mais limpo ter um sistema de mensagens de erro em vez de dois.

Eric Hu
fonte
3
Muito obrigado por isso, eu estava batendo minha cabeça contra a parede por tentar fazer isso.
Lucas
1
Agora, cinco anos depois, essa resposta salvou meu bacon. Muito obrigado @ eric-hu.
marcamillion
12

Eu resolvi isso da mesma forma que o YoyoS, criando app/helpers/devise_helper.rbe colocando isso nele:

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

Trabalhou!

r123454321
fonte
11

Eu só quero trazer um novo pedacinho aqui:

Então, achei uma maneira mais fácil de obter o resultado que "AnApprentice" queria.

Antes de tudo, se você deseja personalizar qualquer coisa no plug-in Devise, recomendo que você copie o código de "\ Ruby_repertory \ lib \ ruby ​​\ gems \ 1.9.1 \ gems \ devise-version \ app \ controllers | helpers | mailers ... "para o arquivo que você deseja em seu projeto.

[Editar] Ou você pode herdar seu arquivo dos arquivos de criação "normais" ... Como ... digamos ... Você deseja substituir apenas uma função dentro de devise / registrations_controller.rb, a primeira linha do seu costume de Usuários controlador de registros seria:

class Users::RegistrationsController < Devise::RegistrationsController

Agora, o Devise fornece uma ferramenta para gerar controladores: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

Então ... enfim ... consegui obter o que "AnApprentice" queria escrever apenas isso (para uma solução mais limpa, veja a grande edição a seguir):

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

      return resource.errors
   end
end

E, na minha opinião, as próximas linhas funcionaram muito bem:

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

Bem ... então você pode acessar os erros de um atributo específico como este:

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

E ... Um pequeno truque para exibir apenas um erro (o primeiro a ser capturado) 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 %>

Eu sei que já faz um tempo desde que essa pergunta foi publicada, mas acho que isso ajudará muitos usuários inventores :).

Grande edição:

Como adoro estender meu código, tornando-o mais limpo e compartilhá-lo com outras pessoas, recentemente desejei alterar as devise_error_messages! para usá-lo em meus pontos de vista e mostrar o truque que expliquei acima.

Então, aqui está o meu 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

Não é grande coisa aqui, reutilizei o código que escrevi na minha exibição para mostrar apenas um atributo pey de erro, porque geralmente o primeiro é o único relevante (como quando o usuário esquece um campo obrigatório).

Estou contando esses erros "únicos" e estou criando um título HTML H2 usando o pluralize e colocando-o ANTES da lista de erros.

Então agora eu posso usar o "devise_error_messages!" como o padrão e renderiza exatamente o que eu já estava renderizando antes.

Se você deseja acessar uma mensagem de erro específica em sua exibição, agora recomendo usar diretamente "resource.errors [: attribute] .first" ou qualquer outra coisa.

Seya, Kulgar.

Kulgar
fonte
6

Estou usando o Devise no Rails 3 e seu código flash é praticamente idêntico ao que eu tenho. No meu aplicativo, o código funciona conforme o esperado; ou seja, as mensagens de erro do Devise são enviadas com o restante das minhas mensagens em flash:

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

Experimente este código exato e veja se faz alguma diferença - o atributo de ID diferente pode ajudar.

Scott
fonte
obrigado, mas isso acaba mostrando nada. "<% = devise_error_messages!%>" gera um erro. o acima não fez nada? Ideias?
AnApprentice
Desculpas - Acabei de ver seu comentário. Para ser sincero, estou ficando sem ideias. Suponho que você tenha visto a fonte no seu navegador e verificado o HTML gerado? Apenas no caso de algo estar oculto pelo CSS. Você está usando a versão mais recente do Devise 1.1.3?
Scott
5

Eu vim para isso e está funcionando até agora. Isso adiciona mensagens planejadas ao flash, para que ele possa ser usado como de costume. Por favor, considere que eu sou novo no 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:

Desculpe, eu estava correndo em guarda e algum comportamento indesejado estava presente. Desde que after_filteré chamado após a renderização, para que não funcione conforme o esperado. Se alguém souber como chamar um método após a ação, mas antes da renderização ...

Mas você pode usar algo assim:

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

No views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->
ddidier
fonte
1
+1 ótima resposta. Eu acho que essa é definitivamente a solução mais limpa e se encaixa perfeitamente na minha arquitetura atual. A resposta não é tão clara - basicamente, tudo antes da edição deve ser ignorado (e removido ou atingido na imo).
Zelanix 14/05
3

Se você deseja exibir mais de um flash de um determinado tipo (: alerta,: aviso, etc ...) e não perder seu tempo tentando modificar o comportamento de uma gema, esta é a solução que usei com o Devise. Tenho certeza de que poderia ser usado com qualquer jóia que use mensagens em flash.

A primeira coisa a fazer, no seu application_controller.rb, adicione isto:

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

Segunda coisa a fazer, exibindo suas mensagens em flash com isso em application.html.erb (ou onde você quiser):

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

Terceira coisa a fazer, sempre que você quiser adicionar uma mensagem flash em qualquer controlador, faça o seguinte:

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

fonte
Mas como fazer com que as mensagens do Devise chamem flash_messages em vez de manter um objeto de erro.
Christopher Oezbek
3

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

Na sua opinião, substitua

<%= devise_error_messages! %>

Para:

<% devise_error_messages! %>
BM
fonte
1
Na verdade, você deve usar: flash.now [: alert]
BM
2

É certo que um pouco hacky, mas estou usando este auxiliar (app / helpers / devise_helper.rb) para capturar flashes e usá-los se definido como padrão resource.errors. Isso é baseado apenas no ajudante que está na lib ideal.

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

Se você estiver olhando para pegar devise_error_messages, poderá adicioná-lo a resource.errors

Se você passasse por cima do controlador de registro, pode 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 
Douglas Drouillard
fonte
2

Maneira muito fácil de exibir mensagem de erro para cada campo

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

coloque para cada campo com o nome do campo entre colchetes abaixo de cada linha em que você deseja exibir a mensagem de erro em linha.

SSR
fonte
1

Para mostrar seu erro de projeto do seu controlador com apenas o primeiro erro para aparecer.

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

Apenas para adicionar à resposta de Eric Hu acima, onde todas as instruções If são usadas, faça algo assim.

# 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
fonte
1

Eu simplesmente faço isso, trabalhei para mim: em app / helpers / , crio um arquivo 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

em todos os arquivos de visualização eu mudo

<%= devise_error_messages! %>

para:

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

para mim, fazer na minha opinião editar e novo usuário:

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

espero que ajude você;)

dev.guillaumem59
fonte
Eu realmente não entendo como isso faz alguma coisa? isso é comportamento padrão? É apenas outra maneira de fazer o <%= devise_error_messages! %>que não responde à pergunta. A pergunta pergunta como aplicar o flash a cada mensagem.
Mark
0
  1. Remova o "devise_error_messages!" do modelo "app / views / users / passwords / new".
  2. Crie um controlador personalizado para seu usuário (app / controllers / users / passwords_controller.rb) e, em um filtro posterior, adicione erros no array flash:
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
fonte
0

Eu gosto de fazê-lo exatamente como foi feito no outro controlador Devise com esse truque.

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

Para materialisecss exibir mensagens de erro planejadas como brinde, adicionei este código em 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

Tenho certeza de que a maneira mais limpa de escrevê-lo, mas está funcionando perfeitamente

Gregoire Mulliez
fonte
0

DeviseHelper#devise_error_messages! foi descontinuado e será removido na próxima versão principal.

O Devise agora usa um parcial devise/shared/error_messagespara exibir mensagens de erro por padrão e torná-las mais fáceis de personalizar. Atualize suas visualizações alterando as chamadas de:

      <%= devise_error_messages! %>

para:

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

Eu apenas criei um app/helpers/devise_helper.rbcomo John, mas substitui o método assim:

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

Com isso, não preciso modificar mais nada. É uma má ideia ? Eu sou novo nos trilhos, não hesite em me corrigir. Obrigado.

YoyoS
fonte
Isso não funcionará como desejado, a mensagem flash agora contém a tag html <br>. Normalmente você só coloca string na sua mensagem flash.
AZ.
Talvez, mas a nova linha ainda funciona. Propor outra solução, se você não gosta desta.
YoyoS
-2

Acabei de declarar devise_error_messages! como um ajudante vazio. E manualmente buscou e manipulou os erros em geral _erros parciais para o meu aplicativo. Parecia a solução mais simples e não preciso examinar todos os arquivos do dispositivo e remover a chamada para o manipulador de erros.

Harry Moreno
fonte