Help us understand the problem. What is going on with this article?

Rails チュートリアル 第8章 まとめ

全体像の把握

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はこうした事態にも対応できる

session.new.html.erb
form_for(:session, url: login_path) do |f|
#form_forの引数にシンボルを渡すことで、@userと同様の効果
# オプション引数はPostリクエストを/loginに送るため
#form_forにはcreateアクションに送る機能があるが、ここではurlを指定する必要がある

createアクション

sessions_controller.
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だと初めてコードを読んだ人にも分かるように、メソッドを定義する

session_helper.
def log_in(user)
  session[:user_id] = user.id
end

user情報が引数で渡されると、そのuserのidがuser_idというキーの値として保存される

sessions_controller
   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情報から復元しなければならない
すると、今ログインしているユーザのページが反映される

sessions_helper
   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で呼び出すことができる

test/fixtures/users.yml
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
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした