切换分支:
git checkout log-in
记忆令牌
生成迁移
rails generate migration add_remember_digest_to_users remember_digest:string
执行迁移
rails db:migrate
生成随机令牌
# app/models/User.rb
... ...
attr_accessor :remember_token
... ...
def User.new_token
SecureRandom.urlsafe_base64
end
# 生成记忆令牌,写入数据库(跳过认证)
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token)) # update_attribute会跳过认证阶段(不知道密码)
end
... ...
登录记住状态
认证令牌
# app/models/user.rb
... ...
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
... ...
辅助方法
# app/helpers/sessions_helper.rb
... ...
# 记住登录状态
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
# signed: 签名
cookies.permanent[:remember_token] = user.remember_token
# cookies[:remember_token] = { value: user.remember_token, expires: 20.years.from_now.utc }
end
# 返回当前登录用户
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
... ...
记住登录状态
# app/controllers/sessions_controller.rb
... ...
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
... ...
忘记用户
# app/models/user.rb
... ...
# 忘记用户
def forget
update_attribute(:remember_digest, nil)
end
... ...
辅助函数
# app/helpers/sessions_helper.rb
... ...
# 忘记用户
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# 退出当前用户
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
... ...
解决问题
多个窗口间退出
编写测试:
# test/integration/users_login_test.rb
... ...
test "login with valid information followed by logout in multi-windows" do
get login_path
post login_path, session: { email: @user.email, password: 'joshua' }
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
# 模拟用户在另一个窗口点击退出
delete logout_path
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
... ...
修改退出方法:
# app/controllers/sessions_controller.rb
... ...
def destroy
log_out if logged_in?
redirect_to root_url
end
... ...
多浏览器间退出
编写测试:
# test/models/user_test.rb
... ...
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('')
end
... ...
修改认证方法:
# app/models/user.rb
... ...
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
... ...
Remember Me
添加复选框
# app/views/sessions/new.html.erb
... ...
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
Remember me?
<% end %>
<%= f.submit "Log in", class: "btn btn-primary" %>
... ...
修改样式
# app/assets/stylesheets/custom.css.scss
... ...
.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}
#session_remember_me {
width: auto;
margin-left: 0;
}
实现功能
# app/controllers/sessions_controller.rb
... ...
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
# remember user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
... ...
测试
编写辅助功能
# test/test_helper.rb
... ...
# 登录测试用户
def log_in_as(user, options = {})
password = options[:password] || 'joshua'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email: user.email, password: password, remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
# 在集成测试中返回true
def integration_test?
defined?(post_via_redirect)
end
... ...
编写测试
# test/integration/users_login_test.rb
... ...
test "login with remembering" do
log_in_as(@user, remember_me: '1')
assert_not_nil cookies['remember_token']
end
test "login without remembering" do
log_in_as(@user, remember_me: '0')
assert_nil cookies['remember_token']
end
... ...
合并删除分支
git checkout master
git merge log-in
git branch -d log-in
git branch
部署
heroku maintenance:on # 开启维护模式
git push heroku master
heroku rake db:migrate
heroku maintenance:off
参考
《Rails Tutorial 3th》