Devise and handling the flash

后端 未结 1 1594
被撕碎了的回忆
被撕碎了的回忆 2021-02-10 08:28

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         


        
1条回答
  •  天涯浪人
    2021-02-10 08:39

    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.

    0 讨论(0)
提交回复
热议问题