# 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
```
- Ruby on Rails 教程
- 致中国读者
- 序
- 致谢
- 作者译者简介
- 版权和代码授权协议
- 第 1 章 从零开始,完成一次部署
- 1.1 简介
- 1.2 搭建环境
- 1.3 第一个应用
- 1.4 使用 Git 做版本控制
- 1.5 部署
- 1.6 小结
- 1.7 练习
- 第 2 章 玩具应用
- 2.1 规划应用
- 2.2 用户资源
- 2.3 微博资源
- 2.4 小结
- 2.5 练习
- 第 3 章 基本静态的页面
- 3.1 创建演示应用
- 3.2 静态页面
- 3.3 开始测试
- 3.4 有点动态内容的页面
- 3.5 小结
- 3.6 练习
- 3.7 高级测试技术
- 第 4 章 Rails 背后的 Ruby
- 4.1 导言
- 4.2 字符串和方法
- 4.3 其他数据类型
- 4.4 Ruby 类
- 4.5 小结
- 4.6 练习
- 第 5 章 完善布局
- 5.1 添加一些结构
- 5.2 Sass 和 Asset Pipeline
- 5.3 布局中的链接
- 5.4 用户注册:第一步
- 5.5 小结
- 5.6 练习
- 第 6 章 用户模型
- 6.1 用户模型
- 6.2 用户数据验证
- 6.3 添加安全密码
- 6.4 小结
- 6.5 练习
- 第 7 章 注册
- 7.1 显示用户的信息
- 7.2 注册表单
- 7.3 注册失败
- 7.4 注册成功
- 7.5 专业部署方案
- 7.6 小结
- 7.7 练习
- 第 8 章 登录和退出
- 8.1 会话
- 8.2 登录
- 8.3 退出
- 8.4 记住我
- 8.5 小结
- 8.6 练习
- 第 9 章 更新,显示和删除用户
- 9.1 更新用户
- 9.2 权限系统
- 9.3 列出所有用户
- 9.4 删除用户
- 9.5 小结
- 9.6 练习
- 第 10 章 账户激活和密码重设
- 10.1 账户激活
- 10.2 密码重设
- 10.3 在生产环境中发送邮件
- 10.4 小结
- 10.5 练习
- 10.6 证明超时失效的比较算式
- 第 11 章 用户的微博
- 11.1 微博模型
- 11.2 显示微博
- 11.3 微博相关的操作
- 11.4 微博中的图片
- 11.5 小结
- 11.6 练习
- 第 12 章 关注用户
- 12.1 “关系”模型
- 12.2 关注用户的网页界面
- 12.3 动态流
- 12.4 小结
- 12.5 练习