I am using Devise 3.1.1 with rails 3 and I have this flash handling code in my layout:
<% flash.each do |name, msg| %>
<%= content_tag :section, msg, :id => "flash_#{name}", :class => "flash" %>
<% end %>
I sign into my app, flash says:
"Signed in successfully."
then sign out, then sign in incorrectly and flash says:
"Signed out successfully."
"Invalid email or password."
I think I understand why I am getting two messages, when signing in incorrectly there is no redirect, just a render.
Not sure how to fix it though.
I figured out the reason.
When you dig into Devise's source of SessionsController, you'll find #create
method as follows:
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
In above code, Devise sets flash message for success signed in here. That's what the message you saw as "Signed in successfully."
. It uses the method set_flash_message
which is just a wrapper of flash[key]= "something"
. The same is true for #destroy
method which show you "Signed out successfully"
.
Note in above code, there is no code to set error message such as "Invalid password or email". So how comes this message you saw? It is set in Devise::FailureApp
def recall
env["PATH_INFO"] = attempted_path
flash.now[:alert] = i18n_message(:invalid)
self.response = recall_app(warden_options[:recall]).call(env)
end
Note here, the method is flash.now
, not flash
. The difference is flash.now
will deliver flash message in current request, not next.
By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the create action fails to save a resource and you render the new template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use flash.now in the same way you use the normal flash. http://guides.rubyonrails.org/action_controller_overview.html#the-flash
So the reason is revealed now.
You signed out. You hit SessionsController#destroy
. Devise destroyed your session, brings you to /users/sign_in
, render 'new
template for your sign in again. The flash object contains the successful signed out message and you saw it.
Now you tried to sign in in same page. This time your form submit hit #create
. If error, Devise will not redirect you to anywhere but render the same 'new'
template again with the flash.now
object which contains sign in error message.
In step 2, you last flash object is not removed because no new request rendered, but another new flash.now
object is added. So you see two message.
Of course it's possible to override Devise to change this behaviour, but that's troublesome and unnecessary.
A more convenient and user-friendly solution is, do not land the user on sign in page after either signed in or signed out.
This is easily by setting store_location
and override after_sign_in_path_for
and after_signed_out_path_for
in your application controller.
def store_location
disable_pattern = /\/users/
session[:previous_url] = request.fullpath unless request.fullpath =~ disable_pattern
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
end
def after_sign_out_path_for(resource)
after_sign_in_path_for(resource)
end
By this setting the user will land on his previously browsed page either after signed in or signed out, and they will not see two flash messages in the question again.
The reason is, when the user signed out, he will be redirect to previous page and see the signed out message. When he want to sign in, he need to go to sign in page which is a new request, then the previous signed out flash will be removed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With