rails - Devise - Handling - devise_error_messages

2020-01-23 10:05发布

in my user edit page, there is a line as follows:

<%= devise_error_messages! %>

The problem is this does not output errors the standard way that the rest of the app does:

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

My question is, how do I get the devise error message to work like the others that use the flash.each?

Thanks.

20条回答
成全新的幸福
2楼-- · 2020-01-23 10:44

I just want to bring a new little piece here:

So I found an easier way to get the result that "AnApprentice" wanted.

First of all, if you want to customize anything within the Devise plug-in, I highly advise you to copy past the code from "\Ruby_repertory\lib\ruby\gems\1.9.1\gems\devise-version\app\controllers|helpers|mailers..." to the file you want in your project.

[Edit] Or you can make your file inherit from the "normal" devise files... Like... say... You want to overwrite only one function within the devise/registrations_controller.rb, the first line of your Users custom registrations controller would be:

class Users::RegistrationsController < Devise::RegistrationsController

[Edit August 7th 2013] Now Devise even provides a tool to generate controllers: https://github.com/plataformatec/devise/wiki/Tool:-Generate-and-customize-controllers

So... anyway... I managed to get what "AnApprentice" wanted just writing this (for a cleaner solution, see the following big edit) :

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

      return resource.errors
   end
end

And, in my view, the next lines worked pretty well:

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

Well... then you can access to errors for a specific attribute like this:

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

And... A little trick to have only one error (the first to get catched) showing up per attribute:

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

I know it's been a while since this question was posted, but I think that it will help lot's of devise users :).

Big Edit:

As I love to extend my code, making it cleaner and share it with others, I recently wanted to change the devise_error_messages! method in order to use it in my views and make it display the trick I explained above.

So, here is my method:

 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 big deal here, I reused the code I wrote in my view to show only one error pey attribute, because often the first one is the only relevant (like when the user forgets one required field).

I'm counting those "unique" errors and I'm making a H2 HTML title using pluralize and putting it BEFORE the errors list.

So now, I can use the "devise_error_messages!" as the default one and it renders exactly what I was already rendering before.

If you want to access a specific error message in your view, I now recommend to use directly "resource.errors[:attribute].first" or whatever.

Seya, Kulgar.

查看更多
萌系小妹纸
3楼-- · 2020-01-23 10:45

I like to do it just like it's done in the other Devise controller with this cheat.

<% 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 %>
查看更多
兄弟一词,经得起流年.
4楼-- · 2020-01-23 10:48

For materialisecss to display devise error messages as toast I added this code in 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

I am sure their would be cleanest way to write it but it's woking perfectly

查看更多
戒情不戒烟
5楼-- · 2020-01-23 10:50

I'm trying to figure this out myself. I just found this issue logged on Github https://github.com/plataformatec/devise/issues/issue/504/#comment_574788

Jose is saying that devise_error_messsages! method is just a stub (though it contains implementation) and that we're supposed to override/replace it. It would have been nice if this was pointed out somewhere in the wiki, which is why i guess there are a few people like us that have been guessing.

So I'm going to try reopening the module and redefine the method, effectively overriding the default implementation. I'll let you know how it goes.

Update

Yep, that works. I created app/helpers/devise_helper.rb and overrode it like so:

module DeviseHelper
  def devise_error_messages!
    'KABOOM!'
  end
end

So knowing this, I can modify the method to display error messages the way I want it to.

To help you solve your original problem: Here's the original devise_helper.rb on Github. Take a look at how the error messages are being traversed:

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

That should help you get started. :)

Another update

The resource object is actually the model that is being used by devise (go figure).

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

It also appears to be defined in a higher scope (probably coming from the controller), so it can be accessed in a variety of places.

Anywhere in your Helper

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

Your View

<div><%= resource.errors.inspect %></div>
查看更多
别忘想泡老子
6楼-- · 2020-01-23 10:51
  1. Remove the "devise_error_messages!" from "app/views/users/passwords/new" template.
  2. Create custom controller for your user (app/controllers/users/passwords_controller.rb) and in an after filter add errors 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
查看更多
Lonely孤独者°
7楼-- · 2020-01-23 10:51

I just declared devise_error_messages! as an empty helper. And manually fetched and handled the errors in a general _errors partial for my application. Seemed like the simplest solution and I don't have to go through all of devise's files and remove the call to the error handler.

查看更多
登录 后发表回答