Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Devise and handling the flash

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.

like image 939
pingu Avatar asked Oct 10 '13 18:10

pingu


1 Answers

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.

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

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

Solution

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.

like image 107
Billy Chan Avatar answered Oct 22 '22 01:10

Billy Chan