【初心者】Rails Sessionについて

# はじめに
Railsチュートリアルの第８章を終えたのでアウトプットとして、Sessionについて投稿します。

#ステートレスとステートフルの違い
インターネットを支えているプロトコルである`HTTP`は、`ステートレス`です。文字通り「状態（state)」がない（less)ので、`HTTPリクエスト`の１つ１つは、それより前のリクエストの情報を引き継いでいません。
以下の例を見るとわかりやすい。

「ステートフルの例」
客: こんにちは
店員: いらっしゃいませ。○○バーガーへようこそ
客: ハンバーガーセットをお願いします
店員: サイドメニューは何になさいますか?
客: ポテトで
店員: ドリンクは何になさいますか?
客: ジンジャーエールで
店員: +50円でドリンクをLサイズにできますがいかがですか?
客: Mでいいです
店員: 以上でよろしいですか?
客: はい
店員: かしこまりました

「ステートレスの例」
客: こんにちは
店員: いらっしゃいませ。○○バーガーへようこそ
客: ハンバーガーセットをお願いします
店員: サイドメニューは何になさいますか?
客: ハンバーガーセットをポテトでお願いします
店員: ドリンクは何になさいますか?
客: ハンバーガーセットをポテトとジンジャーエールでお願いします
店員: +50円でドリンクをLサイズにできますがいかがですか?
客: ハンバーガーセットをポテトとジンジャーエール(M)でお願いします
店員: 以上でよろしいですか?
客: ハンバーガーセットをポテトとジンジャーエール(M)でお願いします。以上
店員: かしこまりました

　`ステートフル`の例では、店員(サーバ)が客(クライアント)の注文状態を覚えている（前の情報を引き継いでいる）。これをアプリケーション状態、あるいはセッション状態と呼ぶ。
　これに対し、`ステートレス`は、注文状態を覚えていません（前の情報を引き継いでいない）ということが分かります。`ステートレス`な場合困る場合があります。それは、`ログイン認証`や`お買い物かご`などの機能を使用する場合です。前の通信を忘れられてしまったら、毎回「ログイン認証する」や「欲しい商品が増えるたび過去に入れた商品も含めて全部を籠に入れ直す」などの処理を行う必要があります。

# セッションとは？
`session`とは、ログインしたあと、ページ遷移しても再度ログインを行わなくても良いように、「ログインしている」ことを記憶しておく機能のこと。つまり、ステートフルな通信を実現するための仕組みである。

# ログイン機能を実装する
　コントローラーとルーティングの作成は省き、ログインフォームの作成から説明します。

　セッションフォームとユーザー登録フォームの最大の違いは、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもない点です。したがって、新しいセッションフォームを作成するときに`form_withヘルパー`に渡さなければならない情報は、若干異なります。

```ruby
form_with(model: @user, local: true)
```

　Railsでは上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースのスコープ（ここではセッション）とそれに対応するURLを具体的に指定する必要があります。

```ruby
form_with(url: login_path, scope: :session, local: true)
```

`scope: :session` => フォームデータが:sessionに入ることに注意。

　次に、ログインフォームに入力された内容をセッションコントローラーのcreateアクションで受け取りユーザーをデータベースから見つけて検証します。

```ruby:sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end
```

```ruby
user = User.find_by(email: params[:session][:email].downcase)
```

①ログインフォームに入力されたEメールアドレスをparamsで受け取り、find_byでデータベースからユーザー情報を取り出し、`downcaseメソッド`を使って、有効なメールアドレスが入力されたときに確実にマッチするようにする。

```ruby
if user && user.authenticate(params[:session][:password])
```

②userがnil出ないかを調べかつ、`authenticateメソッド`で認証します。
<ul>
`authenticateメソッド`は、
<li>誤ったパスワードの場合　→ falseを返す</li>
<li>正しいパスワードの場合　→ そのユーザーを返す</li>
</ul>

# log_inメソッド

```ruby
session[:user_id] = user.id
```

上のコードを実行すると、sessionメソッドでブラウザに保存し、変数のようにブラウザにアクセスできるようになる。

```ruby
# 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
```

# current_user(現在ログイン中のユーザー)

ログインしているんだったら、`current_user`を使ってそのユーザーの情報を返すようにします。また、`if文`を使い、データベースの問い合わせ数を減らします。

```ruby
 # 現在ログイン中のユーザーを返す（いる場合）
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
```

# レイアウトリンクを変更する
　次に、ユーザーがログインしている時とそうでない時でレイアウトを変更します。そのために、ログインしているかしていないかの論理値を返すメソッドが必要なので、それを実装していきます。
　ユーザーがログイン中の状態とは「sessionにユーザーidが存在している」こと、つまり`current_user`が`nil`ではない状態を指します。

```ruby
# ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
```

論理値を返す`logged_in?`メソッドを使い次のようにレイアウトを変更します。

```ruby
<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
<% end %>
```

# ログアウト

ログアウトするには、セッションからユーザーIDを削除します。

```ruby
# 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
```