rails - Devise - Obsługa - devise_error_messages

125

na mojej stronie edycji użytkownika znajduje się następująca linia:

<%= devise_error_messages! %>

Problem polega na tym, że to nie wyświetla błędów w standardowy sposób, w jaki robi to reszta aplikacji:

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

Moje pytanie brzmi: jak sprawić, aby komunikat o błędzie urządzenia działał jak inne, które używają flash.each?

Dzięki.

Uczeń
źródło
1
Należy pamiętać, że Devise używa już flasha, podobnie jak reszta aplikacji. devise_error_messages nie dotyczy wiadomości flash (informacje z ostatniej strony), ale raczej błędy walidacji z ActiveRecord Validation guide.rubyonrails.org/v2.3.11/…
Christopher Oezbek

Odpowiedzi:

135

Próbuję sam to rozgryźć. Właśnie znalazłem ten problem zalogowany na Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

Jose mówi, że devise_error_messsages!metoda jest tylko odgałęzieniem (chociaż zawiera implementację) i że powinniśmy ją przesłonić / zastąpić. Byłoby miło, gdyby zostało to zaznaczone gdzieś na wiki, dlatego myślę, że jest kilka osób takich jak my, które zgadywały.

Dlatego spróbuję ponownie otworzyć moduł i przedefiniować metodę, skutecznie zastępując domyślną implementację. Dam ci znać, jak to idzie.

Aktualizacja

Tak, to działa. Stworzyłem app/helpers/devise_helper.rbi nadpisałem to tak:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

Wiedząc to, mogę zmodyfikować metodę, aby wyświetlać komunikaty o błędach tak, jak chcę.

Aby pomóc Ci rozwiązać pierwotny problem: Oto oryginał devise_helper.rbna Github . Zobacz, w jaki sposób przechodzą komunikaty o błędach:

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

To powinno pomóc ci zacząć. :)

Kolejna aktualizacja

resourceObiekt jest rzeczywiście model, który jest używany przez opracować (przejdź rysunek).

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

Wydaje się również, że jest zdefiniowany w wyższym zakresie (prawdopodobnie pochodzi z kontrolera), więc można uzyskać do niego dostęp w różnych miejscach.

W dowolnym miejscu Twojego pomocnika

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

Twój widok

<div><%= resource.errors.inspect %></div>
Jan
źródło
Właśnie tego próbowałem, ale to nie działa. Celem jest wyświetlenie błędu tutaj: <% flash.each do | key, value | %>
AnApprentice
@ColdTree nie, celem jest, aby działało jak wiadomości flash. Możliwość kontrolowania znaczników jest dobrym rozwiązaniem.
Benjamin Atkin
... Nie sądzę, aby to odpowiadało na pytanie, chociaż jest to dobra praca badawcza.
deivid
37

Poniższe rozwiązanie działa z najnowszym urządzeniem na chwilę obecną (4.1.1) i Railsami 4.2.6. Ale jest tak prosta, że ​​nie widzę powodu, dla którego nie miałoby działać za 10 lat;)

Jeśli chcesz przetworzyć swoje komunikaty o błędach i sprawić, by wyglądały tak samo w całej aplikacji, poleciłbym coś takiego (sposób, którego nauczyłem się z Michaelem Hartlem):

Utwórz część dla komunikatów o błędach: layouts/_error_messages.html.erb Umieść w następującym kodzie (tutaj używam kilku klas 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 %>

Teraz masz coś, co można poddać recyklingowi i możesz tego używać na całej planszy. Zamiast standardowych rozwiązań:

<%= devise_error_messages! %>

Nazwij to w swoim formularzu w ten sposób:

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

Możesz umieścić to w dowolnej formie. Zamiast przekazywać zasób devise, możesz przekazać zmienną ze swojego formularza w następujący sposób:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>
Łukasz Muzyka
źródło
1
Prawdopodobnie najlepsza i najbardziej intuicyjna odpowiedź w historii.
Victor,
2
Fajne rozwiązanie. pluralize (object.errors.count, 'errors' należy zmienić na pluralize (object.errors.count, jednak 'error'
mizurnix
1
@LukaszMuzyka w tym rozwiązaniu .. czy muszę usunąć: validatable z user.rb .. ???
Vishal
1
@Vishal - nie. Powyższe rozwiązanie po prostu używa innego HTML do wyświetlania wiadomości, nie zmienia to żadnej mechaniki Devise
Łukasz Muzyka
1
@Vishal, gdy używasz devise, już wykonuje walidacje, o których wspomniałeś, bez dodatkowego kodu. Powyższe rozwiązanie polega tylko na zastąpieniu domyślnego zachowania urządzenia. W pierwszej kolejności musisz mieć pomysł na pracę. Czy jesteś pewien, czy postępowałeś zgodnie z instrukcjami integracji urządzenia ze swoim projektem?
Łukasz Muzyka
22

Wiem, że minęło trochę czasu od opublikowania tego pytania, ale chciałem tylko skomentować to, co znalazłem. Dwie osoby, które już odpowiedziały, były dla mnie ogromną pomocą i po prostu chciałem wnieść swój wkład.

Zobaczysz w całym Devise, że są używane połączenia render_with_scope. Uważam, że jest to metoda zdefiniowana przez devise i zasadniczo stosuje bieżący zakres do następnego renderowanego widoku.

Dlaczego jest to istotne? Devise zawiera twoje błędy w resource.errors( nie @resource.errors ). Devise działa dobrze, jeśli chcesz go używać po wyjęciu z pudełka, że ​​tak powiem.

Problemy z tymi błędami pojawiają się, gdy zaczniesz zmieniać sposób zarządzania użytkownikami. Dodając redirect_tolub render(zamiast render_with_scope) tam, gdzie Devise wcześniej go nie miał, po prostu wyrzucasz komunikaty o błędach. Moim zdaniem to sprawia, że ​​Devise jest nieprzyjazny dla modyfikacji.

Moje rozwiązanie jest takie

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

i

# 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

Ten ostatni blok kodu przyjmuje komunikaty o błędach Devise jako tablicę i dołącza je do flash[:notice](jako tablica). Każda wiadomość będzie drukowana pojedynczo. Jeśli mam czas, myślę, że zmienię sposób, w jaki Devise obsługuje komunikaty o błędach, aby zrobić to w całej mojej aplikacji, ponieważ wydaje się znacznie czystsze mieć jeden system komunikatów o błędach zamiast dwóch.

Eric Hu
źródło
3
Dziękuję bardzo za to, że waliłam głową o ścianę, próbując to zrobić.
Lucas
1
Minęło 5 lat i ta odpowiedź uratowała mi boczek. Bardzo dziękuję @ eric-hu.
marcamillion
12

Rozwiązałem to podobnie jak YoyoS, tworząc app/helpers/devise_helper.rbi umieszczając w nim to:

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

Pracowałem!

r123454321
źródło
11

Chcę tylko przynieść tutaj nowy mały kawałek:

Więc znalazłem łatwiejszy sposób na uzyskanie wyniku, którego chciał "AnApprentice".

Przede wszystkim, jeśli chcesz dostosować cokolwiek we wtyczce Devise, zdecydowanie radzę skopiować poza kod z "\ Ruby_repertory \ lib \ ruby ​​\ gems \ 1.9.1 \ gems \ devise-version \ app \ controllers | helpers | mailers ... ”do pliku, który chcesz umieścić w projekcie.

[Edytuj] Lub możesz sprawić, by twój plik dziedziczył po "normalnych" plikach urządzenia ... Na przykład ... powiedz ... Chcesz nadpisać tylko jedną funkcję w devise / registrations_controller.rb, pierwszym wierszu niestandardowego użytkownika Administratorem rejestracji byłby:

class Users::RegistrationsController < Devise::RegistrationsController

[Edytuj 7 sierpnia 2013 r.] Now Devise udostępnia nawet narzędzie do generowania kontrolerów: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

Więc ... w każdym razie ... udało mi się uzyskać to, czego chciał "AnApprentice", po prostu pisząc to (aby uzyskać czystsze rozwiązanie, zobacz następującą dużą edycję):

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

      return resource.errors
   end
end

Moim zdaniem następne wiersze działały całkiem nieźle:

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

Cóż ... wtedy możesz uzyskać dostęp do błędów dla określonego atrybutu, takiego jak ten:

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

I ... Mała sztuczka polegająca na wyświetlaniu tylko jednego błędu (pierwszego wyłapanego) dla każdego atrybutu:

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

Wiem, że minęło trochę czasu od opublikowania tego pytania, ale myślę, że pomoże to wielu wymyślonym użytkownikom :).

Duża edycja:

Ponieważ uwielbiam rozszerzać swój kod, czynić go czystszym i dzielić się nim z innymi, ostatnio chciałem zmienić devise_error_messages! metody, aby użyć jej w moich widokach i wyświetlić sztuczkę, którą wyjaśniłem powyżej.

Oto moja metoda:

 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

Nic wielkiego, ponownie wykorzystałem kod, który napisałem, aby pokazać tylko jeden atrybut błędu pey, ponieważ często pierwszy jest jedynym istotnym (np. Gdy użytkownik zapomina o jednym wymaganym polu).

Liczę te "unikalne" błędy i tworzę tytuł HTML dla H2 używając liczby mnogiej i umieszczam go PRZED listą błędów.

Więc teraz mogę użyć komunikatu „devise_error_messages!” jako domyślny i renderuje dokładnie to, co renderowałem wcześniej.

Jeśli chcesz uzyskać dostęp do konkretnego komunikatu o błędzie w swoim widoku, teraz zalecam bezpośrednie użycie „resource.errors [: atrybut] .first” lub cokolwiek innego.

Seya, Kulgar.

Kulgar
źródło
6

Używam Devise w Rails 3 i twój kod flash jest prawie identyczny z tym, co mam. W mojej aplikacji kod działa zgodnie z oczekiwaniami; tzn. komunikaty o błędach Devise są wyprowadzane z resztą moich wiadomości flash:

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

Wypróbuj dokładnie ten kod i zobacz, czy to robi różnicę - może pomóc inny atrybut ID.

Scott
źródło
dzięki, ale to nic nie pokazuje. „<% = devise_error_messages!%>” generuje błąd. powyższe nic nie dało? pomysły?
AnApprentice
Przepraszam - dopiero widziałem Twój komentarz, szczerze mówiąc, kończą mi się pomysły. Zakładam, że przeglądałeś źródło w przeglądarce i sprawdziłeś wygenerowany kod HTML? Na wypadek gdyby coś zostało ukryte przez CSS. Czy używasz najnowszej wersji Devise 1.1.3?
Scott
5

Podszedłem do tego i jak na razie działa. Dodaje to wiadomości do pamięci flash, dzięki czemu można go używać jak zwykle. Proszę wziąć pod uwagę, że jestem nowy w Ruby i 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

Edytować:

Przepraszam, że biegałem na straży i pojawiło się jakieś niepożądane zachowanie. Ponieważ after_filterjest wywoływana po renderowaniu, więc nie działa zgodnie z oczekiwaniami. Jeśli ktoś wie, jak wywołać metodę po akcji, ale przed renderowaniem ...

Ale zamiast tego możesz użyć czegoś takiego:

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

W views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->
ddidier
źródło
1
+1 Świetna odpowiedź. Myślę, że to zdecydowanie najczystsze rozwiązanie i ładnie pasuje do mojej obecnej architektury. Odpowiedź nie jest jednak taka jasna - w zasadzie wszystko przed edycją należy zignorować (i usunąć lub przekreślić imo).
zelanix
3

Jeśli chcesz mieć możliwość wyświetlania więcej niż jednego błysku danego typu (: alert,: notice, itp.) I nie tracić czasu na modyfikowanie zachowania klejnotów, to jest rozwiązanie, które zastosowałem w Devise. Jestem prawie pewien, że można go użyć z każdym klejnotem, który używa wiadomości flash.

Pierwszą rzeczą do zrobienia w pliku application_controller.rb jest dodanie tego:

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

Drugą rzeczą do zrobienia jest wyświetlenie wiadomości flash w pliku application.html.erb (lub gdziekolwiek chcesz):

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

Trzecia rzecz do zrobienia, gdy chcesz dodać wiadomość flash do dowolnego kontrolera, zrób to:

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

źródło
Ale jak sprawić, by komunikaty Devise wywoływały flash_messages zamiast utrzymywać obiekt błędu.
Christopher Oezbek,
3

Utwórz 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

Twoim zdaniem zastąp

<%= devise_error_messages! %>

Do:

<% devise_error_messages! %>
BM
źródło
1
Właściwie powinieneś użyć: flash.now [: alert]
BM
2

Trzeba przyznać, że trochę zepsuty, ale używam tego pomocnika (app / helpers / devise_helper.rb) do przechwytywania błysków i używania ich, jeśli są ustawione, a następnie domyślnie resource.errors. Jest to oparte na pomocniku, który znajduje się w devise 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
typoneerror
źródło
2

Jeśli chcesz się połączyć z devise_error_messages, możesz to zrobić, dodając do resource.errors

Gdybyś miał przejechać kontroler rejestracji, mogłoby to wyglądać

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
źródło
2

Bardzo łatwy sposób wyświetlania komunikatu o błędzie dla każdego pola

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

umieść dla każdego pola z nazwą pola w nawiasach kwadratowych poniżej każdego wiersza, w którym chcesz wyświetlić komunikat o błędzie.

SSR
źródło
1

Aby pokazać błąd urządzenia z kontrolera, z wyświetleniem tylko pierwszego błędu.

flash[:error] = @resource.errors.full_messages.first
Nacięcie
źródło
1

Aby dodać do powyższej odpowiedzi Erica Hu, w której są używane wszystkie stwierdzenia If, zrób raczej coś takiego.

# 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
źródło
1

po prostu to robię, pracował dla mnie: w app / helpers / , tworzę plik 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

we wszystkich plikach widoku, które zmieniam

<%= devise_error_messages! %>

dla:

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

u mnie to zrobić w moim widoku edycję i nowego użytkownika:

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

mam nadzieję, że ci to pomoże;)

dev.guillaumem59
źródło
Naprawdę nie rozumiem, jak to coś robi? to jest standardowe zachowanie? To tylko inny sposób zrobienia tego <%= devise_error_messages! %>i nie odpowiada na pytanie. Pytanie dotyczy tego, jak zastosować flash do każdej wiadomości.
Mark
0
  1. Usuń komunikat „devise_error_messages!” z szablonu „aplikacja / widoki / użytkownicy / hasła / nowe”.
  2. Utwórz niestandardowy kontroler dla swojego użytkownika (app / controllers / users / passwords_controller.rb) i w filtrze po dodaniu błędów 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
Gacha
źródło
0

Lubię to robić tak, jak robi się to w innym kontrolerze Devise z tym kodem.

<% 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
źródło
0

Aby materialisecss wyświetlał komunikaty o błędach devise jako toast, dodałem ten kod w 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

Jestem pewien, że to byłby najczystszy sposób, aby to napisać, ale jest doskonale

Gregoire Mulliez
źródło
0

DeviseHelper#devise_error_messages! jest przestarzały i zostanie usunięty w następnej głównej wersji.

Devise używa teraz częściowego under, devise/shared/error_messagesaby domyślnie wyświetlać komunikaty o błędach i ułatwić ich dostosowywanie. Zaktualizuj swoje widoki zmieniając połączenia z:

      <%= devise_error_messages! %>

do:

      <%= render "devise/shared/error_messages", resource: resource %>
Muhammad Nasir Shamshad
źródło
-1

Właśnie stworzyłem app/helpers/devise_helper.rbpodobnego Johna, ale zastąpiłem taką metodę:

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

Dzięki temu nie muszę nic więcej modyfikować. Czy to zły pomysł? Jestem nowy w railach, nie wahaj się mnie poprawić. Dzięki.

YoyoS
źródło
Nie będzie to działać zgodnie z oczekiwaniami, ponieważ wiadomość flash zawiera teraz znacznik HTML <br>. Zwykle w wiadomości flash umieszczasz tylko ciąg znaków.
AZ.
Być może, ale nowa linia nadal działa. Zaproponuj inne rozwiązanie, jeśli nie podoba Ci się to.
YoyoS
-2

Właśnie zadeklarowałem devise_error_messages! jako pusty pomocnik. I ręcznie pobrałem i obsłużono błędy w części ogólnej _errors dla mojej aplikacji. Wydawało się, że jest to najprostsze rozwiązanie i nie muszę przeglądać wszystkich plików devise i usuwać wywołania do obsługi błędów.

Harry Moreno
źródło