企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 8.3 退出 [8.1 节](#sessions)说过,我们要实现的认证系统会记住用户的登录状态,直到用户自行退出为止。本节,我们就要实现退出功能。退出链接已经定义好了([代码清单 8.16](#listing-layout-login-logout-links)),所以我们只需编写一个正确的控制器动作,销毁用户会话。 目前为止,会话控制器的动作都遵从了 REST 架构,`new` 动作用于登录页面,`create` 动作完成登录操作。我们要继续使用 REST 架构,添加一个 `destroy` 动作,删除会话,实现退出功能。登录功能在[代码清单 8.13](#listing-log-in-success) 和[代码清单 8.22](#listing-login-upon-signup) 中都用到了,但退出功能不同,只在一处使用,所以我们会直接把相关的代码写在 `destroy` 动作中。[8.4.6 节](#remember-tests)会看到,这么做(稍微重构后)易于测试认证系统。 退出要撤销 `log_in`([代码清单 8.12](#listing-log-in-function))完成的操作,即从会话中删除用户的 ID。为此,我们要使用 `delete` 方法,如下所示: ``` session.delete(:user_id) ``` 我们还要把当前用户设为 `nil`。不过在现在这种情况下做不做这一步都没关系,因为退出后会立即转向根地址。[[13](#fn-13)]和 `log_in` 及相关的方法一样,我们要把 `log_out` 方法放在会话辅助方法模块中,如[代码清单 8.26](#listing-log-out-method) 所示。 ##### 代码清单 8.26:`log_out` 方法 app/helpers/sessions_helper.rb ``` module SessionsHelper # 登入指定的用户 def log_in(user) session[:user_id] = user.id end . . . # 退出当前用户 def log_out session.delete(:user_id) @current_user = nil end end ``` 然后,在会话控制器的 `destroy` 动作中调用 `log_out` 方法,如[代码清单 8.27](#listing-destroy-session) 所示。 ##### 代码清单 8.27:销毁会话(退出用户) app/controllers/sessions_controller.rb ``` class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out redirect_to root_url end end ``` 我们可以在[代码清单 8.20](#listing-user-login-test-valid-information) 中的用户登录测试中添加一些步骤,测试退出功能。登录后,使用 `delete` 方法向退出地址([表 8.1](#table-restful-sessions))发起 `DELETE` 请求,然后确认用户已经退出,而且重定向到了根地址。我们还要确认出现了登录链接,而且退出和资料页面的链接消失了。测试中新加入的步骤如[代码清单 8.28](#listing-user-logout-test) 所示。 ##### 代码清单 8.28:测试用户退出功能 GREEN test/integration/users_login_test.rb ``` require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "login with valid information followed by logout" do get login_path post login_path, session: { email: @user.email, password: 'password' } assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 end end ``` (现在可以在测试中使用 `is_logged_in?` 了,所以向登录地址发送有效信息之后,我们添加了 `assert is_logged_in?`。) 定义并测试了 `destroy` 动作之后,注册、登录和退出三大功能就都实现了。现在测试组件应该可以通过: ##### 代码清单 8.29:**GREEN** ``` $ bundle exec rake test ```