#全体像の把握
login-logout情報をrails serverとブラウザに一時的に保存するためSessionを使う
Sessionsコントローラーを作り、ルーティング、コントローラー、テンプレートを作成
createアクションでは、フォームで入力された内容をparams[:session][:email],params[:session][:password]として取得し、authenticateメソッドで正しいユーザーか認証する。
loginに成功したら、loginしているUser、していないUserで振る舞いを変える。しているUserには、Profileやlogoutを表示する。
#session情報を取り扱うコントローラを作成
Sessionクラスを作っていないので、Userコントローラnewアクションのように、
@session = Session.new
とはできない
form_forはこうした事態にも対応できる
form_for(:session, url: login_path) do |f|
#form_forの引数にシンボルを渡すことで、@userと同様の効果
# オプション引数はPostリクエストを/loginに送るため
#form_forにはcreateアクションに送る機能があるが、ここではurlを指定する必要がある
createアクション
def create
@user = User.find_by(email: params[:session][:email])
#form_forでユーザが入力した内容がparamsに格納されている
#emailを手がかりにUser情報を取得、それを@userに代入
if @user.authenticate(params[:session][:password])
#.auth-は第6章終わりで学んだ、passwordが正しいか認証する
#DBのpassword(@user)と今打ち込んだフォームのpasswordが同じかどうか確かめている
end
しかし、このコードでは登録されていないemailが入力された場合、find_by()は見つからなかった時、nilを返すので@user = nilということになり、 nilにはauthe-メソッドはないので、Errorとなる
そこで
def create
@user = User.find_by(email: params[:session][:email])
if @user && @user.authenticate(params[:session][:password])
end
とする
こうすることで、if文はnilとfalse以外は全てtrueなので、
もし@userにユーザオブジェクトが入っているなら、if文はtrueになるし、
@userにnilが入っているなら、trueとはならないので、elseの方に行く
さらに、@userを左に置くことで、@user.athen-の方は評価されないためErrorにならない
#flashが消えないバグ
flash[:]の有効期間はリクエストが発行されるまでで、今まではredirect_toをすぐ実行していたので消えていた
#ログイン成功時、ログイン状態の保持
sessionを使えば、serverに一時的に情報を保存することができる
まずその機能の実装
loginだと初めてコードを読んだ人にも分かるように、メソッドを定義する
def log_in(user)
session[:user_id] = user.id
end
user情報が引数で渡されると、そのuserのidがuser_idというキーの値として保存される
def create
user = User.find_by(email: params[:session][:email])
if user && user.authenticate(params[:session][:password])
log_in user # これはメソッドの引数としてuser情報を用意している、sessionに情報を保管
redirect_to user #こっちのuserはuser_path(showページ)を表す
else
flash.now[:danger] = "Invalid"
render "new"
end
end
#sessionの情報がある時(login状態)、ない時(logout状態)で振る舞いを変える実装
profileページでは他人のプロフィールではなく、自分のプロフィールが表示されないといけないので、自分の情報を引っ張ってくる必要がある
そのため今ログインしている人のユーザーオブジェクトをsession情報から復元しなければならない
すると、今ログインしているユーザのページが反映される
def current_user
@current_user = User.find(session[:user_id])
# session情報からUser情報を復元
# showテンプレートで使うので@currentとする
end
findは失敗した時にErrorを返す、sessionが切れるたびにエラーになると面倒
find_byは失敗した時にnilを返す、もう一度ログインしてもらえば良くなる
find => find_byにする
ただ、その人がログインしているかチェックするたびにfind_byを投げるのは、パフォーマンス上、問題
def current_user
if @current_user == nil
#もしsessionが切れていたら
@current_user = User.find_by(id: session[:user_id] #User情報の復元
else
#もし一度でもfind_byかけて復元していたら、その時の記憶から@current_userを呼び出す
@current_user
end
end
def current_user
@current_user = @current_user || @current_user = User.find_by(id: session[:user_id]
# or演算子はnilかfalseの時だけ右側に行くので、もしsession情報があれば左
# sessionが切れてnilが入っていれば右側に行く
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
session[:user_id]が保持されているかいないかで振る舞いを変える部分
def logged_in?
!current_user.nil?
# loginしていたらtrue、していなかったらfalseを
# current_userにsession情報があれば、nilではないのでfalseになるが
# !でその情報を反転させてtrueを返すようにしている
end
Helperは対応するViewで使うことが意図されているが、今回はコントローラで使う(log_in)
なので、コントローラでinclude SessionsHelper
を
成功する時のTest(難しい)
テストで開発環境を再現する
has_secureを使うことで
password_digest.authenticate(password)
という形が取れる
DBに入っているのは、password_digestでしかなく、ハッシュ値であるため、本来入力された完全なpasswordと一致しないはずだが、has_scureの効果でpassword_digestのハッシュ化前の値と照合できる
このテストでは、ハッシュ化されたpassword_digestを再現することがポイント
def setup
@user = users(:michael)
# usersメソッドの引数にラベル(micael)をおくと、サンプルデータが手に入る
end
.
.
.
test "login with valid information" do
get login_path #loginページの表示
post login_path, params: { session: { email: @user.email,
password: 'password' } }
# 今までのTestなら適当な値でもフォーマットが正しければTestとして機能していたが
# passwordはauthen-を使うので、正しい値、実際の値を使わなければ通らない(?)
assert_redirected_to @user
# リダイレクト先が正しいかどうかをチェック
follow_redirect!
# 実際にリダイレクト先に移動
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
# loginへのリンクがないことを確認
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
end
test用のデータを作りたい時に使うファイルがある
テスト用のサンプルデータセットをfixtureに書くと、これがテストのデータベースの中にサンプルデータとして入る
password_digestはハッシュ値であり、そのハッシュ値とbscryptをかけて平文をハッシュ値に変換したものを照合する必要がある
ここで作ったmichaelは上のsetupで呼び出すことができる
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
そのためのメソッドUser.digestを作る
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
# BCrypt::Password.create(stringで文字列をハッシュ値に変換
# costはハッシュ変換をライトに行うという指示
end
log-out
def log_out
session.delete(:user_id)
# keyを指定すると、該当するvalueを削除する
@current_user = nil
end