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

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

createアクション

```rb: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となる

そこで

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

```rb:session_helper.
def log_in(user)
  session[:user_id] = user.id
end
```
user情報が引数で渡されると、そのuserのidがuser_idというキーの値として保存される

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

```ruby: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を投げるのは、パフォーマンス上、問題

```rb:
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
```

```rb:
def current_user
  @current_user = @current_user || @current_user = User.find_by(id: session[:user_id]
# or演算子はnilかfalseの時だけ右側に行くので、もしsession情報があれば左
# sessionが切れてnilが入っていれば右側に行く
end
```

```ruby:
def current_user
   @current_user ||= User.find_by(id: session[:user_id])
end
```

session[:user_id]が保持されているかいないかで振る舞いを変える部分

```rb:
 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を再現することがポイント

```rb:
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
```