rails - Devise - Handling - devise_error_messages
Asked Answered
P

22

128

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.

Pulsatile answered 4/11, 2010 at 21:36 Comment(1)
Please note that Devise is already using the flash as the rest of the app is doing. devise_error_messages is not about flash messages (information from the last page), but rather validation errors from ActiveRecord Validation guides.rubyonrails.org/v2.3.11/…Misdemeanant
S
135

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>
Streptomycin answered 30/11, 2010 at 2:56 Comment(3)
I just tried this but that doesn't work. The goal is to get the error to output here: <% flash.each do |key, value| %>Pulsatile
@ColdTree no, the goal is for it to work like the flash messages. Being able to control the markup is a good solution.Stooge
... I don't think this answers the question although it is a good research work.Foursquare
S
38

Below solution works with latest devise as of now (4.1.1) and Rails 4.2.6. But is so simple that I don't see the reason why wouldn't it work 10 years from now;)

If you want to recycle your error messages and have them look the same across your app I would recommend something like this (way I have learned with Michael Hartl tut):

Create partial for error messages: layouts/_error_messages.html.erb Put inside following code (here I use some bootstrap 3 classes):

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

Now you have something recyclable and you can use it across the board. Instead of standard devise:

<%= devise_error_messages! %>

Call it in your form like this:

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

You can put it in any form. Instead of passing devise resource you can pass variable from your form like this:

<%= form_for @post do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>  
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>
Shardashare answered 19/12, 2013 at 23:24 Comment(10)
Probably the best and most intuitive answer ever.Brice
Cool solution. pluralize(object.errors.count, 'errors' should be changed to pluralize(object.errors.count, 'error' thoughGaffer
@LukaszMuzyka in this solution .. do i need to remove :validatable from user.rb..???Boni
@Boni - no. Above solution simply uses different HTML to display the messages it doesn't change any mechanics of DeviseShardashare
@LukaszMuzyka thnx for reply .. i just added it.. but its not working.. i want a email and password presence validation . and show error message if email or password is empty...Boni
@Boni and the validations don't work? How does that manifest itself?Shardashare
@LukaszMuzyka i added validates :email, presence: true in user.rb. bro in short i have to show message email cant be blank or password cant be blank. if email or password is empty.. but by default devise. always show invalid email or password. that what i want to change.. show blank error message if email or password is blank. else show invalid email or password. !! waiting for your response. !!Boni
@Boni when you're using devise it's already doing the validations you have mentioned without any extra code. Above solution is only to override default devise behavior. You have to have devise working in the first place. Are you sure if you have followed instructions to integrate devise with your project?Shardashare
@LukaszMuzyka i want to add the message email cant be blank..if and only email is empty while login. by default.. devise always show "invalid email or password." if email is empty and or invalid.. thats what i want.. and yes i followed the instructions to integrate devise. but i want to change default behaviour of error messages. ! do you get my question.. ?Boni
Let us continue this discussion in chat.Shardashare
A
22

I know it's been a while since this question was posted, but I just wanted to comment on what I've found. The two people who've already answered have been a tremendous help to me and I just wanted to contribute.

You'll see throughout Devise that there are calls using render_with_scope. I believe this is a method defined by devise and basically applies the current scope to the next view rendered.

Why is this relevant? Devise contains your errors within resource.errors (not @resource.errors). Devise works fine if you want to use it out of the box, so to speak.

Problems with these errors arise if you start changing your user management behavior. By adding a redirect_to or render (instead of render_with_scope) where Devise previously didn't have one, you're basically tossing out the error messages. This makes Devise unfriendly to modification, in my opinion.

My solution is this

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

and

# 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

The latter code block takes Devise's error messages as an array and appends it to flash[:notice] (as an array). Each message will be printed out one line at a time. If I have the time, I think I'm going to change how Devise handles error messages to do this throughout my app, as it seems much cleaner to have one error message system instead of two.

Alb answered 6/4, 2011 at 0:55 Comment(2)
Thank you so much for that, I was banging my head against the wall for trying to do that.Yonne
It is now 5 years later and this response saved my bacon. Thanks very much @eric-hu.Signalize
C
12

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.

Commencement answered 5/7, 2011 at 14:56 Comment(0)
F
12

I solved this similarly to YoyoS, by creating an app/helpers/devise_helper.rb and placing this in it:

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

Worked!

Fourwheeler answered 30/12, 2013 at 7:40 Comment(0)
D
6

I'm using Devise in Rails 3 and your flash code is pretty much identical to what I've got. In my app, the code works as expected; i.e. Devise error messages are output with the rest of my flash messages:

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

Try out this exact code and see if it makes any difference - the different ID attribute may help.

Doggy answered 5/11, 2010 at 9:59 Comment(2)
thanks but that ends up showing nothing. "<%= devise_error_messages! %>" outputs an error. the above did nothing? ideas?Pulsatile
Apologies - I've only just seen your comment.To be honest, I'm running out of ideas. I assume you've viewed source in your browser and checked the HTML that's generated? Just in case something's being hidden by CSS. Are you using the latest version of Devise 1.1.3?Doggy
C
5

I came up to this and it's working so far. That adds devise messages to the flash, so it can be used as usual. Please consider that I'm new to 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

Edit:

Sorry I was running guard and some unwanted behavior was present. Since after_filter is called after the rendering so it doesn't work as expected. If someone know how to call a method after the action but before the rendering...

But you can use something like that instead:

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

In views/shared/_messages.html.erb

<% devise_flash %>
<!-- then display your flash messages as before -->
Chandigarh answered 13/4, 2012 at 21:14 Comment(1)
+1 Great answer. I think that this is definitely the cleanest solution and fits nicely into my current architecture. The answer is not so clear though - basically everything before the edit should be ignored (and removed or struck through imo).Apogee
I
3

If you want to be able to display more than one flash of a given type (:alert, :notice, etc...) and not waste your time trying to modify a gem behavior, this is the solution I used with Devise. I'm pretty sure it could be used with any gem that uses flash messages.

First thing to do, in your application_controller.rb, add this:

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

Second thing to do, displaying your flash messages with this in application.html.erb (or wherever you want):

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

Third thing to do, whenever you want to add a flash message in any controller, do this:

flash_message(:success, "The user XYZ has been created successfully.")
Iapetus answered 24/5, 2013 at 15:26 Comment(1)
But how to get the Devise messages to call flash_messages instead of keeping an error object.Misdemeanant
D
3

Create 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

In your view, substitute

<%= devise_error_messages! %>

To:

<% devise_error_messages! %>
Dioxide answered 15/10, 2018 at 19:11 Comment(1)
Actually, you should use: flash.now[:alert]Dioxide
S
2

Admittedly, a bit hacky, but I'm using this helper (app/helpers/devise_helper.rb) to grab flashes and use those if set then default to resource.errors. This is just based on the helper that's in the 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
Stencil answered 29/11, 2011 at 22:48 Comment(0)
H
2

If you are looking to piggyback off of devise_error_messages then you can so by adding to resource.errors

If you were to over ride the registration controller, it might look like

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 
Hollah answered 21/10, 2012 at 1:54 Comment(0)
V
2

Very easy way to display error message for each field

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

put for each field with field name in square bracket below every line where u want to display inline error message.

Viewpoint answered 8/10, 2013 at 10:7 Comment(0)
F
1

To show your devise error from your controller with only the first error to showing up.

flash[:error] = @resource.errors.full_messages.first
Frill answered 13/10, 2011 at 0:47 Comment(0)
B
1

Just to add to Eric Hu answer above where all the If statements are used, rather do something like this instead.

# 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 %>
Broaden answered 21/1, 2012 at 18:49 Comment(0)
D
1

i simply do this, worked for me: in app/helpers/, i create a file 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

in all view files i change

<%= devise_error_messages! %>

for:

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

for me it make in my view edit and new user:

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

hope it will help you ;)

Deeannadeeanne answered 12/2, 2017 at 23:33 Comment(1)
I really don't understand how this does anything? this is standard behaviour? It's just another way of doing the <%= devise_error_messages! %> and doesn't answer the question. The question asks how to apply flash to each message.Helman
Q
1

DeviseHelper#devise_error_messages! is deprecated and will be removed in the next major version.

Devise now uses a partial under devise/shared/error_messages to display error messages by default, and make them easier to customize. Update your views changing calls from:

      <%= devise_error_messages! %>

to:

      <%= render "devise/shared/error_messages", resource: resource %>
Quantifier answered 24/7, 2020 at 23:7 Comment(0)
C
0
  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
Circumgyration answered 3/8, 2012 at 13:55 Comment(0)
S
0

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 %>
Stav answered 17/7, 2015 at 7:8 Comment(0)
E
0

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

Equalitarian answered 22/2, 2018 at 9:28 Comment(0)
M
0

Easy, place the following code in views/devise/sessions/new.html.erb

<% if flash[:alert] %>
 <div class='alert alert-danger'><%= flash[:alert] %></div>
<% end %>

And that's it!

Mccann answered 28/6, 2021 at 21:56 Comment(1)
This was soo much helpfulConscionable
D
-1

I just created an app/helpers/devise_helper.rb like John but overrode the method like that :

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

With this I don't have to modify anything else. Is it a bad idea ? I'm new to rails, don't hesitate to correct me. Thanks.

Doy answered 18/11, 2013 at 14:48 Comment(2)
This won't work as desired the flash message now contains html tag <br>. Normally you only put string in your flash message.Synder
Perhaps, but the new line still works. Propose another solution if you don't like this one.Doy
R
-2

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.

Regatta answered 19/7, 2013 at 1:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.