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.


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>

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

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.


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!


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.


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.


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

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.")

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

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

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 

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.


1

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

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

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

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


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

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

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


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

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


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.