railsチュートリアル　第九章　


##はじめに
railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

##Remember me 機能

 ```[remember me] 機能``` を利用すると、ユーザーが明示的にログアウトを実行しない限り、ログイン状態を維持することができるようになる。

この機能を利用するには、**記憶トークン (remember token)**を生成し、cookiesメソッドによる**永続的cookiesの作成**や、安全性の高い**記憶ダイジェスト (remember digest)** によるトークン認証にこの記憶トークンを活用することが必要となる。

（トークンとは、パスワードと似たようなものであるが、違いとしてはトークンの場合はコンピュータが作成した情報である）

永続的セッションを作成するにあたって、以下のようなことを留意点とする。

>・記憶トークンにはランダムな文字列を生成して用いる。
・ブラウザのcookiesにトークンを保存するときには、有効期限を設定する。
・トークンはハッシュ値に変換してからデータベースに保存する。
・ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
・永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する。

###rememberダイジェストを追加
最初の手順として、マイグレーションファイルを生成し、```remember_digest属性(string)```を**Userモデル**に追加する。

また、remember_digestはユーザーが直接生み出すことはないので、インデックスを追加する必要がない。

###記憶トークンの作成
記憶トークンには基本的に長くてランダムな文字列が求められるので今回は**SecureRandomモジュール**にある ```urlsafe_base64メソッド``` を使用する。


このメソッドは、A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる**長さ22のランダムな文字列**を返す。
例：

```
$ rails console
>> SecureRandom.urlsafe_base64
=> "q5lt38hQDc_959PVoo6b7A"
```

ユーザーを記憶するには、記憶トークンを作成して、そのトークンをダイジェストに変換したものをデータベースに保存すればいい。

ここで、トークン生成用のメソッドを追加する。

```app/models/user.rb
# ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end
```

次に有効なトークンとそれに関連するダイジェストを作成する。
具体的には、最初に ```User.new_token``` で記憶トークンを作成し、続いて ```User.digest``` を適用した結果で記憶ダイジェストを更新する。

```app/models/user.rb
attr_accessor :remember_token
.
.
.
 # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end
```

```rememberメソッド``` の1行目の代入の箇所では、```self``` というキーワードを使わないと、Rubyによって**remember_tokenという名前のローカル変数**が作成されてしまう。
selfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
rememberメソッドの２行目では、```update_attributeメソッド``` を使って記憶ダイジェストを更新している。


###ログイン状態の保持
```user.rememberメソッド``` が動作するようになったので、ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができた。
これを実行するには ```cookiesメソッド``` を使う。

以下に手順を示す。

```app/models/user.rb
.
.
.

 # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
```

```rememberヘルパーメソッド``` を追加して、```log_in``` と連携させる。

```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
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
 end
```

実際のSessionsヘルパーの動作は、rememberメソッド定義のuser.rememberを呼び出すまで遅延され、そこで記憶トークンを生成してトークンのダイジェストをデータベースに保存される。

続いて上と同様に、cookiesメソッドでユーザーIDと記憶トークンの永続cookiesを作成する。

```app/helpers/sessions_helper.rb
module SessionsHelper

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

  # ユーザーのセッションを永続的にする
  #追加
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

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

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

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

これにより、```current_userヘルパー``` を追加修正する必要がある。

記憶トークンcookieに対応するユーザーを返す↓

```app/helpers/sessions_helper.rb
# 記憶トークンcookieに対応するユーザーを返す
  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
```

###ユーザーを忘れる
ユーザーがログアウトできるように、ユーザーを記憶するためのメソッドと同様の方法で、ユーザーを忘れるためのメソッドを定義する。

具体的には、**記憶ダイジェストをnilで更新するメソッドを定義すること**となる。

```forgetヘルパーメソッド``` を追加して、```log_outヘルパーメソッド```から呼び出す。


```app/models/user.rb
.
.
.
# ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
end
```


```app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
  .
  .
  .
  # 永続的セッションを破棄する
  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
end
```


###[Remember me] チェックボックス

ログインフォームに ```チェックボックス``` を追加する。
チェックボックスは、他のラベル、テキストフィールド、パスワードフィールド、送信ボタンと同様にヘルパーメソッドで作成できる。

```app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>
      #追加
      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>
```

2つのCSSクラス ```checkbox``` ```inline``` を使い、**Bootstrap**ではこれらをチェックボックスとテキスト「Remember me on this computer」として同じ行に配置することができる。

最後に、チェックボックスがオンのときにユーザーを記憶し、オフのときには記憶しないようにする。

ログインフォームから送信されたparamsハッシュには既にチェックボックスの値が含まれていて、オンの時は１、オフの時は０になるので、三項演算子を使うことで、コンパクトに処理を書くことができる。

```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
      #追加
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
end
```

ちなみに三項演算子を使わない場合は追加部分は以下のようなコードとなる。

```
if params[:session][:remember_me] == '1'
  remember(user)
else
  forget(user)
end
```

##おわりに
普段、当たり前のようにお世話になっている機能がここまで難しとは、、という感じです、、、
徐々に理解していこうと思います！

次↓
https://qiita.com/jonnyjonnyj1397/items/dda1a3b33bf9a1c0f982