рельсы - Devise - Обработка - devise_error_messages

125

на моей странице редактирования пользователя есть следующая строка:

<%= devise_error_messages! %>

Проблема в том, что это не выводит ошибки стандартным образом, как это делает остальная часть приложения:

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

У меня вопрос: как заставить сообщение об ошибке devise работать, как другие, использующие flash.each?

Спасибо.

AnApprentice
источник
1
Обратите внимание, что Devise уже использует флеш-память, как и все остальное приложение. devise_error_messages не о флэш-сообщениях (информация с последней страницы), а об ошибках валидации из руководств по
Кристофер Озбек,

Ответы:

135

Я сам пытаюсь в этом разобраться. Я только что обнаружил, что эта проблема зарегистрирована на Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

Хосе говорит, что devise_error_messsages!метод - это всего лишь заглушка (хотя и содержит реализацию), и мы должны переопределить / заменить его. Было бы хорошо, если бы на это указали где-нибудь в вики, поэтому я думаю, что есть несколько людей, подобных нам, которые гадают.

Итак, я попытаюсь снова открыть модуль и переопределить метод, фактически переопределив реализацию по умолчанию. Я дам вам знать, как это происходит.

Обновить

Ага, это работает. Я создал app/helpers/devise_helper.rbи переопределил его так:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

Итак, зная это, я могу изменить метод для отображения сообщений об ошибках так, как я хочу.

Чтобы помочь вам решить исходную проблему: вот оригинал devise_helper.rbна Github . Взгляните, как просматриваются сообщения об ошибках:

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

Это должно помочь вам начать работу. :)

Еще одно обновление

resourceОбъект на самом деле модель , которая используется по завещанию (Go Figure).

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

Он также, похоже, определен в более высокой области (вероятно, исходящий от контроллера), поэтому к нему можно получить доступ из различных мест.

Где угодно в вашем помощнике

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

Ваше мнение

<div><%= resource.errors.inspect %></div>
Джон
источник
Я просто попробовал это, но это не сработало. Цель состоит в том, чтобы вывести здесь ошибку: <% flash.each do | key, value | %>
AnApprentice
@ColdTree нет, цель состоит в том, чтобы он работал как флеш-сообщения. Возможность контролировать разметку - хорошее решение.
Бенджамин Аткин
... Я не думаю, что это ответ на вопрос, хотя это хорошая исследовательская работа.
deivid
37

Нижеприведенное решение работает с последней версией на данный момент (4.1.1) и Rails 4.2.6. Но это настолько просто, что я не вижу причины, по которой это не сработает через 10 лет;)

Если вы хотите переработать свои сообщения об ошибках и сделать так, чтобы они выглядели одинаково в вашем приложении, я бы порекомендовал что-то вроде этого (как я узнал с Майклом Хартлом tut):

Создайте партиал для сообщений об ошибках: layouts/_error_messages.html.erb вставьте следующий код (здесь я использую несколько классов начальной загрузки 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 %>

Теперь у вас есть что-то пригодное для вторичной переработки, и вы можете использовать его повсеместно. Вместо стандартного устройства:

<%= devise_error_messages! %>

Назовите это в вашей форме так:

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

Вы можете оформить его в любом виде. Вместо передачи ресурса разработки вы можете передать переменную из формы следующим образом:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>
Лукаш Музыка
источник
1
Вероятно, лучший и самый интуитивно понятный ответ.
Виктор
2
Классный раствор. pluralize (object.errors.count, 'errors' следует заменить на множественное (object.errors.count, 'error', хотя
mizurnix
1
@LukaszMuzyka в этом решении .. мне нужно удалить: validatable из user.rb .. ???
Vishal
1
@Vishal - нет. Вышеупомянутое решение просто использует другой HTML для отображения сообщений, это не меняет механику Devise
Лукаш Музыка
1
@Vishal, когда вы используете devise, уже выполняет упомянутые вами проверки без дополнительного кода. Вышеупомянутое решение - только переопределить поведение устройства по умолчанию. Прежде всего, вы должны иметь работающее устройство. Вы уверены, что следовали инструкциям по интеграции разработки в свой проект?
Лукаш Музыка
22

Я знаю, что этот вопрос был давно опубликован, но я просто хотел прокомментировать то, что нашел. Два человека, которые уже ответили, оказали мне огромную помощь, и я просто хотел внести свой вклад.

Вы увидите, что в Devise используются вызовы render_with_scope. Я считаю, что это метод, определенный в devise, и в основном применяет текущую область видимости к следующему отображаемому представлению.

Почему это актуально? Devise содержит ваши ошибки внутри resource.errorsне @resource.errors ). Devise отлично работает, если вы хотите использовать его, так сказать, из коробки.

Проблемы с этими ошибками возникают, если вы начинаете изменять свое поведение управления пользователями. Добавляя redirect_toили render(вместо render_with_scope) там, где у Devise его раньше не было, вы в основном отбрасываете сообщения об ошибках. На мой взгляд, это делает Devise неприемлемым для модификации.

Мое решение таково

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

и

# 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

Последний блок кода принимает сообщения об ошибках Devise в виде массива и добавляет его flash[:notice](как массив). Каждое сообщение будет распечатываться по одной строке за раз. Если у меня будет время, я думаю, что собираюсь изменить способ обработки сообщений об ошибках в Devise, чтобы делать это во всем моем приложении, поскольку гораздо удобнее иметь одну систему сообщений об ошибках вместо двух.

Эрик Ху
источник
3
Большое вам спасибо за это, я бился головой о стену за попытку сделать это.
Лукас
1
Прошло 5 лет, и этот ответ спас мой бекон. Большое спасибо @ eric-hu.
marcamillion
12

Я решил это аналогично YoyoS, создав app/helpers/devise_helper.rbи поместив в него следующее:

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

Работал!

r123454321
источник
11

Я просто хочу принести сюда новый кусочек:

Итак, я нашел более простой способ получить результат, который хотел «AnApprentice».

Прежде всего, если вы хотите настроить что-либо в подключаемом модуле Devise, я настоятельно рекомендую вам скопировать код из "\ Ruby_repertory \ lib \ ruby ​​\ gems \ 1.9.1 \ gems \ devise-version \ app \ controllers". | helpers | mailers ... "в файл, который вы хотите включить в свой проект.

[Edit] Или вы можете сделать свой файл наследованием от "обычных" файлов devise ... Например ... скажем ... Вы хотите перезаписать только одну функцию в devise / registrations_controller.rb, первой строке вашего пользовательского файла. Контроллер регистрации будет:

class Users::RegistrationsController < Devise::RegistrationsController

[Редактировать 7 августа 2013 г.] Теперь Devise даже предоставляет инструмент для создания контроллеров: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

Итак ... в любом случае ... Мне удалось получить то, что хотел AnApprentice, просто написав это (более чистое решение см. В следующем большом редактировании):

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

      return resource.errors
   end
end

И, на мой взгляд, очень хорошо работали следующие строки:

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

Что ж ... тогда вы можете получить доступ к ошибкам для определенного атрибута, например:

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

И ... Небольшой трюк, чтобы отображалась только одна ошибка (первая обнаруженная) для каждого атрибута:

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

Я знаю, что этот вопрос был опубликован некоторое время, но я думаю, что он поможет многим пользователям изобретать :).

Большое редактирование:

Поскольку я люблю расширять свой код, делая его чище и делиться им с другими, я недавно захотел изменить файл devise_error_messages! , чтобы использовать его в своих представлениях и отображать трюк, который я объяснил выше.

Итак, вот мой метод:

 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

Здесь нет ничего страшного, я повторно использовал код, который написал в моем представлении, чтобы показать только один атрибут ошибки pey, потому что часто первый из них является единственным актуальным (например, когда пользователь забывает одно обязательное поле).

Я считаю эти "уникальные" ошибки и делаю заголовок H2 HTML, используя множественное число и помещаю его ПЕРЕД списком ошибок.

Итак, теперь я могу использовать "devise_error_messages!" по умолчанию, и он отображает именно то, что я уже делал раньше.

Если вы хотите получить доступ к конкретному сообщению об ошибке в вашем представлении, я рекомендую использовать напрямую "resource.errors [: attribute] .first" или что-то еще.

Сея, Кульгар.

Kulgar
источник
6

Я использую Devise в Rails 3, и ваш флэш-код практически идентичен тому, что есть у меня. В моем приложении код работает должным образом; т.е. сообщения об ошибках Devise выводятся вместе с остальными моими флэш-сообщениями:

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

Попробуйте использовать этот точный код и посмотрите, имеет ли он какое-либо значение - может помочь другой атрибут ID.

Скотт
источник
спасибо, но это ничего не показывает. «<% = devise_error_messages!%>» выводит ошибку. выше ничего не сделал? идеи?
AnApprentice 05
Извините - я только что увидел ваш комментарий. Если честно, у меня заканчиваются идеи. Я полагаю, вы просмотрели исходный код в своем браузере и проверили созданный HTML? На всякий случай что-то скрывает CSS. Вы используете последнюю версию Devise 1.1.3?
Скотт
5

Я подошел к этому, и пока это работает. Это добавляет во флеш-память сообщения о разработке, так что ее можно использовать как обычно. Учтите, что я новичок в Ruby и 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

Редактировать:

Извините, я был на посту охраны, и было какое-то нежелательное поведение. Поскольку after_filterвызывается после рендеринга, он работает не так, как ожидалось. Если кто-то знает, как вызвать метод после действия, но до рендеринга ...

Но вместо этого вы можете использовать что-то вроде этого:

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

В views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->
ddidier
источник
1
+1 Отличный ответ. Я думаю, что это определенно самое чистое решение, которое хорошо вписывается в мою текущую архитектуру. Однако ответ не так однозначен - в основном все, что до редактирования, следует игнорировать (и удалять или вычеркивать через imo).
zelanix
3

Если вы хотите иметь возможность отображать более одной вспышки определенного типа (: alert,: notice и т. Д.) И не тратить время на попытки изменить поведение драгоценного камня, это решение, которое я использовал с Devise. Я почти уверен, что его можно использовать с любым драгоценным камнем, который использует флеш-сообщения.

Первым делом добавьте в свой application_controller.rb следующее:

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

Второе, что нужно сделать, отобразить ваши флэш-сообщения с этим в application.html.erb (или где угодно):

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

В-третьих, когда вы хотите добавить флэш-сообщение в любой контроллер, сделайте следующее:

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

источник
Но как заставить сообщения Devise вызывать flash_messages вместо сохранения объекта ошибки.
Кристофер Озбек
3

Создать 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

На ваш взгляд, заменить

<%= devise_error_messages! %>

Для того, чтобы:

<% devise_error_messages! %>
BM
источник
1
На самом деле, вам следует использовать: flash.now [: alert]
BM
2

По общему признанию, немного хакерский, но я использую этот помощник (app / helpers / devise_helper.rb), чтобы захватывать вспышки и использовать их, если установлено, а затем по умолчанию resource.errors. Это просто основано на помощнике, который находится в 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
typeoneerror
источник
2

Если вы хотите использовать devise_error_messages вместе с devise_error_messages, вы можете это сделать, добавив в resource.errors

Если бы вы перебрали контроллер регистрации, это могло бы выглядеть так:

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

Очень простой способ отобразить сообщение об ошибке для каждого поля

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

поместите для каждого поля имя поля в квадратные скобки под каждой строкой, где вы хотите отображать встроенное сообщение об ошибке.

SSR
источник
1

Чтобы показать ошибку вашего устройства с вашего контроллера с появлением только первой ошибки.

flash[:error] = @resource.errors.full_messages.first
Ник
источник
1

Просто чтобы добавить к ответу Эрика Ху выше, где используются все операторы If, вместо этого сделайте что-то вроде этого.

# 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
источник
1

Я просто делаю это, у меня сработало: в app / helpers / я создаю файл 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

во всех файлах просмотра я меняю

<%= devise_error_messages! %>

для:

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

для меня это сделать в моем представлении редактирования и нового пользователя:

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

надеюсь, это поможет вам;)

dev.guillaumem59
источник
Я правда не понимаю, как это на что-то влияет? это стандартное поведение? Это просто еще один способ сделать это <%= devise_error_messages! %>и не отвечает на вопрос. Вопрос спрашивает, как применить флеш к каждому сообщению.
Марк
0
  1. Удалите сообщение "devise_error_messages!" из шаблона "приложение / просмотры / пользователи / пароли / новый".
  2. Создайте настраиваемый контроллер для вашего пользователя (app / controllers / users / passwords_controller.rb) и в фильтре после добавления флеш-массива ошибок:
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
источник
0

Мне нравится делать это так же, как это делается в другом контроллере Devise с этим читом.

<% 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
источник
0

Чтобы Materialisecss отображал сообщения об ошибках разработки в виде тоста, я добавил этот код в 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

Я уверен, что это был бы самый чистый способ написать это, но он отлично работает

Грегуар Муллиес
источник
0

DeviseHelper#devise_error_messages! устарела и будет удалена в следующей основной версии.

Devise теперь devise/shared/error_messagesпо умолчанию использует частичное отображение сообщений об ошибках и упрощает их настройку. Обновите свои представления, изменив звонки с:

      <%= devise_error_messages! %>

чтобы:

      <%= render "devise/shared/error_messages", resource: resource %>
Мухаммад Насир Шамшад
источник
-1

Я просто создал app/helpers/devise_helper.rbпохожего на Джона, но переопределил такой метод:

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

При этом мне больше ничего не нужно изменять. Это плохая идея? Я новичок в рельсах, не стесняйтесь поправлять меня. Спасибо.

Yoyos
источник
Это не сработает должным образом, флэш-сообщение теперь содержит тег html <br>. Обычно вы вставляете только строку во флеш-сообщение.
АЗ.
Возможно, но новая линия все еще работает. Предложите другое решение, если оно вам не нравится.
YoyoS
-2

Я только что объявил devise_error_messages! как пустой помощник. И вручную получил и обработал ошибки в общем партиале _errors для моего приложения. Казалось, что это самое простое решение, и мне не нужно просматривать все файлы devise и удалять вызов обработчика ошибок.

Гарри Морено
источник