💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 8.6 练习 电子书中有练习的答案,如果想阅读参考答案,请[购买电子书](http://railstutorial-china.org/#purchase)。 避免练习和正文冲突的方法参见[3.6 节](chapter3.html#mostly-static-pages-exercises)中的说明。 1. 在[代码清单 8.32](#listing-user-model-remember) 中,我们定义了生成令牌和摘要的类方法,前面都加上了 `User`。这么定义没问题,而且因为我们会使用 `User.new_token` 和 `User.digest` 调用,或许这样定义意思更明确。不过,定义类方法有两种更常用的方式,一种有点让人困惑,一种极其让人困惑。运行测试组件,确认[代码清单 8.59](#listing-token-digest-self)(有点让人困惑)和[代码清单 8.60](#listing-token-digest-class-self)(极其让人困惑)中的实现方式是正确的。(注意,在[代码清单 8.59](#listing-token-digest-self) 和[代码清单 8.60](#listing-token-digest-class-self) 中,`self` 是 `User` 类,而用户模型中的其他 `self` 都是用户对象实例。这就是让人困惑的根源所在。) 2. [8.4.5 节](#remember-me-checkbox)说过,由于应用现在的设计方式,在[代码清单 8.51](#listing-remember-me-test) 的集成测试中无法获取 `remember_token` 虚拟属性。不过,在测试中使用一个特殊的方法可以获取,这个方法是 `assigns`。在测试中,可以访问控制器中定义的实例变量,方法是把实例变量的符号形式传给 `assigns` 方法。例如,如果 `create` 动作中定义了 `@user` 变量,在测试中可以使用 `assigns(:user)` 获取这个变量。现在,会话控制器中的 `create` 动作定义了一个普通的变量(不是实例变量),名为 `user`,如果我们把它改成实例变量,就可以测试 `cookies` 中是否包含用户的记忆令牌。填写[代码清单 8.61](#listing-login-create-user-instance) 和[代码清单 8.62](#listing-improved-remember-me-test) 中缺少的内容(`?` 和 `FILL_IN`),完成改进后的“记住我”复选框测试。 ##### 代码清单 8.59:使用 `self` 定义生成令牌和摘要的方法 GREEN app/models/user.rb ``` class User < ActiveRecord::Base . . . # 返回指定字符串的哈希摘要 def self.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # 返回一个随机令牌 def self.new_token SecureRandom.urlsafe_base64 end . . . end ``` ##### 代码清单 8.60:使用 `class &lt;&lt; self` 定义生成令牌和摘要的方法 GREEN app/models/user.rb ``` class User < ActiveRecord::Base . . . class << self # 返回指定字符串的哈希摘要 def digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # 返回一个随机令牌 def new_token SecureRandom.urlsafe_base64 end end . . . ``` ##### 代码清单 8.61:在 `create` 动作中使用实例变量的模板 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 params[:session][:remember_me] == '1' ? remember(?user) : forget(?user) redirect_to ?user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out if logged_in? redirect_to root_url end end ``` ##### 代码清单 8.62:改进后的“记住我”复选框测试模板 GREEN test/integration/users_login_test.rb ``` require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "login with remembering" do log_in_as(@user, remember_me: '1') assert_equal assigns(:user).FILL_IN, FILL_IN end test "login without remembering" do log_in_as(@user, remember_me: '0') assert_nil cookies['remember_token'] end . . . end ```