8.3 退出

8.1 节说过,我们要实现的认证系统会记住用户的登录状态,直到用户自行退出为止。本节,我们就要实现退出功能。退出链接已经定义好了(代码清单 8.16),所以我们只需编写一个正确的控制器动作,销毁用户会话。

目前为止,会话控制器的动作都遵从了 REST 架构,new 动作用于登录页面,create 动作完成登录操作。我们要继续使用 REST 架构,添加一个 destroy 动作,删除会话,实现退出功能。登录功能在代码清单 8.13代码清单 8.22 中都用到了,但退出功能不同,只在一处使用,所以我们会直接把相关的代码写在 destroy 动作中。8.4.6 节会看到,这么做(稍微重构后)易于测试认证系统。

退出要撤销 log_in代码清单 8.12)完成的操作,即从会话中删除用户的 ID。为此,我们要使用 delete 方法,如下所示:

session.delete(:user_id)

我们还要把当前用户设为 nil。不过在现在这种情况下做不做这一步都没关系,因为退出后会立即转向根地址。[13]和 log_in 及相关的方法一样,我们要把 log_out 方法放在会话辅助方法模块中,如代码清单 8.26 所示。

代码清单 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 所示。

代码清单 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 中的用户登录测试中添加一些步骤,测试退出功能。登录后,使用 delete 方法向退出地址(表 8.1)发起 DELETE 请求,然后确认用户已经退出,而且重定向到了根地址。我们还要确认出现了登录链接,而且退出和资料页面的链接消失了。测试中新加入的步骤如代码清单 8.28 所示。

代码清单 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