Railsチュートリアル 第9章 発展的なログイン機構 - [Remember me] チェックボックス

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

## 完成状態のモックアップ

Railsチュートリアル本文では、[図 9.3](https://railstutorial.jp/chapters/advanced_login?version=5.1#fig-login_remember_me_mockup)が完成状態のモックアップとして示されています。

## ログインフォームにチェックボックスを追加する

チェックボックスの埋め込みRubyコードは以下です。

```
<%= f.label :remember_me, class: "checkbox inline" do %>
  <%= f.check_box :remember_me %>
  <span>Remember me on this computer</span>
<% end %>
```

上記コードの重要なポイントは以下です。

* Railsにおけるフォームのチェックボックスは、埋め込みRubyのヘルパーメソッドで作成できる
    * メソッドのスペルは`checkbox`ではなく`check_box`である
        * HTMLの属性名は`checkbox`なので、混同に注意
    * 他のラベル・テキストフィールド・パスワードフィールド・送信ボタンと同様
* チェックボックスが正常に動作するためには、ラベルの内側にチェックボックスを配置する必要がある
* CSSクラスとしては`checkbox`と`inline`を使っている
    * チェックボックスとラベルテキストを同じ行に配置する
    * 適切なCSSクラスを使うようにすれば、基本的な見た目の定義はBootstrapにおまかせ

ログインフォームのHTMLを生成するコードの実体は`app/views/sessions/new.html.erb`です。以下のように変更を実装します。

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

## CSSルールの追加

マージンや表示フォント等、もう少し見た目を整えるために、`app/assets/stylesheets/custom.scss`にもう少しCSSルールを追加します。変更内容は以下のとおりです。

```diff:app/assets/stylesheets/custom.scss
  @import "bootstrap-sprockets";
  @import "bootstrap";

  ...略

  /* forms */

  ...略
+
+ .checkbox {
+     margin-top: -10px;
+     margin-bottom: 10px;
+     span {
+         margin-left: 20px;
+         font-weight:  normal;
+     }
+ }
+
+ #session_remember_me {
+     width: auto;
+     margin-left:  0;
+ }

  ...略
```

## 編集後のログインフォームの見た目

編集後のログインフォームをWebブラウザで表示すると、表示結果は以下のようになります。

<img width="1158" alt="スクリーンショット 2019-11-11 8.03.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/160af989-07e8-8adf-d343-8322ce296360.png">

パスワードフィールドと送信ボタンの間に、「Remember me on this computer」というラベルのついたチェックボックスが追加されていますね。

## [remember me]チェックボックスに、実際の動作を実装する

ここまでRailsチュートリアルを学習してきた結果、以下の実装が既に存在します。

* `SessionsHelper#remember`メソッド
* `SessionsHelper#forget`メソッド

また、ログインフォームの「Log in」ボタンをクリックすると、チェックボックスのチェック状態がHTTPのリクエスト内容に反映されるという実装も既に実現されています。

ゆえに、[remember me]チェックボックスに実際の動作を実装する作業は、以下の1行のコードを書くだけで済むのです。

```ruby:
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
```

### 三項演算子`?:`

上記1行による実装は、`?:`という三項演算子を使っています。より具体的には、`<条件式> ? <真式> : <偽式>`という書き方をしています。以下のコードと同じ動作になりますが、今回のような場合、三項演算子を用いたコードのほうがより「Rubyらしい」コード[^Rubyらしいコード]といえるのでないでしょうか。

[^Rubyらしいコード]: 特にRubyの場合、真偽値を返すメソッドは末尾に`?`をつける慣習があるので、`?`の後に真偽値による分岐処理が書かれるのは自然なこととして受け入れられやすいと思います。

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

### 実際にコードを追加する

場所は`app/controllers/sessions_controller.rb`の`create`アクション内です。

```diff:app/controllers/sessions_controller.rb
  class SessionsController < ApplicationController
    ...略

    def create
      user = User.find_by(email: params[:session][:email].downcase)
      if user && user.authenticate(params[:session][:password])
        log_in user
-       remember(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

    ...略
  end
```

# 演習 - [Remember me] チェックボックス

## 1. ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。

まず、「Remember me on this computer」にチェックを付けずにログインしてみます。

<img width="1168" alt="スクリーンショット 2019-11-11 12.39.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/a57df231-ae4c-f147-75b8-4cb9523e4ef7.png">

このとき、以下のように永続cookiesは生成されていません。

<img width="917" alt="スクリーンショット 2019-11-11 12.39.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/c456eab5-21d9-151d-6feb-5b419130850b.png">

一旦ログアウトした後、今度は「Remember me on this computer」にチェックを付けてログインしてみます。

<img width="1198" alt="スクリーンショット 2019-11-11 12.41.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/5b4d5463-8a3e-ab16-4c7d-13a5ad26133f.png">

今度は以下のように2039年を有効期限とする永続cookiesが生成されました。

<img width="919" alt="スクリーンショット 2019-11-11 12.41.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/f9c87646-a2e8-b90a-058f-f04146dbe0f4.png">


## 2. コンソールを開き、三項演算子を使った実例を考えてみてください ([コラム 9.2](https://railstutorial.jp/chapters/advanced_login?version=5.1#aside-ternary_operator))。

```
>> Time.new.sunday? || Time.new.saturday? ? 'Weekend' : 'Weekday'
=> "Weekday"
```

↑システム日時が土曜日か日曜日なら"Weekend"を返し、それ以外の曜日なら"Weekday"を返します。

```
>> Time.new.hour >= 12 ? 'PM' : 'AM'
=> "AM"
```

↑システム時刻が12時以降なら"PM"を返し、そうでなければ"AM"を返します。