8.3 退出
最后更新于:2022-04-01 22:29:37
# 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
```
';