我明白了原因。
当你深入研究 Devise 的 SessionsController 源码时,你会发现#create
方法如下:
# 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
在上面的代码中,Devise 设置了此处登录成功的闪现消息。这就是您看到的消息"Signed in successfully."
。它使用的方法set_flash_message
这只是一个包装flash[key]= "something"
。对于#destroy
向您展示的方法"Signed out successfully"
.
注意上面的代码中有没有代码来设置错误消息例如“密码或电子邮件无效”。那么你是怎么看到这条消息的呢?它设置在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
这里注意一下,方法是flash.now
, not flash
。区别在于flash.now
将在当前请求中传递 Flash 消息,而不是下一个请求。
默认情况下,将值添加到闪存将使它们可用于下一个请求,但有时您可能希望在同一请求中访问这些值。例如,如果创建操作无法保存资源并且您直接渲染新模板,则不会产生新请求,但您可能仍希望使用 flash 显示消息。为此,您可以像使用普通闪光灯一样使用 flash.now。http://guides.rubyonrails.org/action_controller_overview.html#the-flash http://guides.rubyonrails.org/action_controller_overview.html#the-flash
那么原因现在就揭晓了。
你退出了。你击中了SessionsController#destroy
。设计破坏了你的会话,让你/users/sign_in
, 使成为'new
供您再次登录的模板。 Flash 对象包含成功注销的消息,您可以看到它。
现在您尝试在同一页面登录。这次你的表单提交成功了#create
。如果出现错误,Devise 不会将您重定向到任何地方,而是呈现相同的内容'new'
再次使用模板flash.now
包含登录错误消息的对象。
在步骤 2 中,最后一个 Flash 对象不会被删除,因为没有呈现新的请求,而是另一个新的请求flash.now
对象已添加。所以你会看到两条消息。
Solution
当然可以重写 Devise 来改变这种行为,但这很麻烦而且不必要.
更方便和用户友好的解决方案是,登录或注销后不要让用户进入登录页面.
这很容易通过设置store_location
并覆盖after_sign_in_path_for
and after_signed_out_path_for
在您的应用程序控制器中。
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
通过此设置,用户在登录或注销后将登陆之前浏览过的页面,并且他们不会再次在问题中看到两条闪现消息。
原因是,当用户注销时,他将被重定向到上一页并看到注销消息。当他想要登录时,他需要进入登录页面,这是一个新的请求,那么之前登录的Flash将被删除。