【Rails】Docker上のアプリケーションでDevise（認証機能）を使う方法

Docker上のRailsアプリケーションでDevise（認証機能）を使う方法のまとめ。



##参考リンク

- https://github.com/heartcombo/devise
- https://github.com/heartcombo/devise/wiki/How-Tos
- https://github.com/heartcombo/devise/wiki/How-To:-Add-:confirmable-to-Users


<br>
##(はじめに）dockerコンテナ内でコマンドを実行する方法
Dockerコンテナの中のアプリケーションでコマンドを実行する場合はまずコンテナに入る。

コンテナの中に入るには**`docker`コマンドと`docker-compose`使う方法がある**。

どちらの場合もコンテナは起動している必要がある。

```
docker exec -it サービス名 シェル
```

```
docker-compose exec サービス名 シェル
```

シェルは基本的に`bash`か`sh`。アプリケーション毎に異なる。


<br>
**▼実例**
例えば、alpine（Linuxディストリビューションの一つ）のrubyを使って作成したdocker上のrailsアプリケーションに入る場合は以下となる。

```
$ docker exec -it サービス名 sh
/app # 
```

または

```
$ docker-compose exec サービス名 sh
/app #
```


`/app # `が表示されればコンテナ内に入れている。コンテナから出る時は`exit`を入力しEnter。




<br>
##ログイン機能の作成方法
ここから本題。railsの`devise`というgemを使ってログイン認証機能を簡単に実装する手順。


##1. deviseのインストール
Gemfileにdeviseを追記し、`bundle install`を実行する。

```Gemfile:Gemfile
gem 'devise'
```

railsはdockerの中で起動しているので、docker内のコンテナに入ってから`bundle install`を実行する。

```
$ docker exec -it サービス名 sh
/app # bundle install
```

以下のように表示されればdeviseパッケージのインストールが完了。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/494b54fd-addc-3b1d-997a-97690ee8020a.png)


##2. deviseをアプリケーションに導入する
deviseのパッケージインストールが終わったので、次にアプリケーションにdevise機能を実装する。Dockerコンテナに入ったまま`rails g devise:install`を実行。

```sh:
/app # rails g devise:install

Running via Spring preloader in process 534
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

     * Not required *

===============================================================================
```

これでdeviseが使えるようになった。


<br>
##3. 認証機能が使えるモデルの作成
認証機能が使えるモデルを作成するには、通常の`rails g model モデル名`ではなく`devise`を使う。

・`rails g devise モデル名`

```
/app # rails g devise user
Running via Spring preloader in process 572
      invoke  active_record
      create    db/migrate/20210414012052_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users
```


<br>
##3-1. migrationファイルの中身
db/migrate/20210414012052_devise_create_users.rb

devise_create_テーブル名　のクラスが生成され、関数の中には各モジュール毎の設定が記述されている。

```rb:
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

```

カラムの型の記述ルールは以下のようになっている。

```
t.カラムの型 :カラム名 条件1: 値1, 条件2: 値2,,,
```


<br>
####3-1-1. Database authenticatable

```rb:
t.string :email,              null: false, default: ""
t.string :encrypted_password, null: false, default: ""
```

emailと暗号化したパスワードのカラムを作成している。
`null: false`　何かしら値がない場合はエラーとなる。
`default: ""`　初期値は何も指定されていない。



####3-1-2. Recoverable

```rb:
t.string   :reset_password_token
t.datetime :reset_password_sent_at
```

パスワードリセット用のトークンを保存するカラムと、送信日時を保存するカラム。


<br>
####3-1-3. Rememberable

```rb:
t.datetime :remember_created_at
```

ログイン状態を維持しておく期間。



####3-1-4. Trackable

```rb:
t.integer  :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string   :current_sign_in_ip
t.string   :last_sign_in_ip
```

ログインした回数、最新のログイン日時、前回のログイン日時、最新のログインIP、最終のログインIPを保存。

デフォルトはコメントアウト。



####3-1-5. Confirmable

```rb:
t.string   :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string   :unconfirmed_email # Only if using reconfirmable
```

メールに記載されているURLをクリックして登録完了する時に使うカラム。

確認用の番号（トークン）、登録申請された時間、本登録用のメールを送信した時間、e-mailアドレスを保存する。

デフォルトはコメントアウト。




####3-1-6. Lockable

```rb:
t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string   :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
```

一定回数ログインに失敗した時にロックアウトする用のカラム。

ログインの入力可能回数、ロックアウトしたトークン、ロックアウトした時間を保存。


デフォルトはコメントアウト。


####3-1-7. 重複防止のバリデーション（ユニーク設定）
emailやトークンなど重複してはいけないカラムを指定する。

・`add_index :テーブル名, :カラム名, unique: true`

```rb:
add_index :users, :email,                unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token,   unique: true
# add_index :users, :unlock_token,         unique: true
```

<br>
####（補足）追加モジュール
デフォルトでは記述がないが、以下のようなモジュールも用意されている。使う場合は追加でカラムの設定が必要。

・`timeoutable`
一定時間活動していないアカウントのセッションを破棄する。デフォルトは30分が設定されている。

変更したい場合はconfig/initializers/devise.rbに追記する。


・`omniauthable`
SNS認証機能（twitterやfacebookなど）の追加。intridea/omniauthを使えるようにする。




<br>
##3-2. コントローラの中身
コントローラの中で使いたいモジュールを`:モジュール名`で指定する。

app/models/user.rb

```rb:user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
```

モジュールは複数指定可能。


<br>
##3-3. ルーティングの確認
config/routes.rb　を確認すると`devise_for: users`が追加されている。

・`devise_for: モデル名`　指定したモデル名を使ったルーティングを自動設定してくれる。

`/users/sign_in`や`/users/password/new`といったルートが作成されている。


```:追加されたルーティング一覧
/app # rails routes
                                  Prefix Verb   URI Pattern                                                                                       Controller#Action
                        new_user_session GET    /users/sign_in(.:format)                                                                          devise/sessions#new
                            user_session POST   /users/sign_in(.:format)                                                                          devise/sessions#create
                    destroy_user_session DELETE /users/sign_out(.:format)                                                                         devise/sessions#destroy
                       new_user_password GET    /users/password/new(.:format)                                                                     devise/passwords#new
                      edit_user_password GET    /users/password/edit(.:format)                                                                    devise/passwords#edit
                           user_password PATCH  /users/password(.:format)                                                                         devise/passwords#update
                                         PUT    /users/password(.:format)                                                                         devise/passwords#update
                                         POST   /users/password(.:format)                                                                         devise/passwords#create
                cancel_user_registration GET    /users/cancel(.:format)                                                                           devise/registrations#cancel
                   new_user_registration GET    /users/sign_up(.:format)                                                                          devise/registrations#new
                  edit_user_registration GET    /users/edit(.:format)                                                                             devise/registrations#edit
                       user_registration PATCH  /users(.:format)                                                                                  devise/registrations#update
                                         PUT    /users(.:format)                                                                                  devise/registrations#update
                                         DELETE /users(.:format)                                                                                  devise/registrations#destroy
                                         POST   /users(.:format)                                                                                  devise/registrations#create
```



<br>
##4. development.rbの設定
config/environments/development.rb　に、デフォルトのURLを設定する。

```rb:development.rb
#devise setting
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
```


<br>
##5. デフォルトの認証ページ画面を表示する
マイグレーションをしてから、認証ページにアクセスする。

Dockerコンテナ内で`rails db:migrate`を実行。

```
/app # rails db:migrate
== 20210414012052 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0302s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0041s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0036s
== 20210414012052 DeviseCreateUsers: migrated (0.0382s) =======================
```


<br>
ブラウザで追加されたルートにアクセスする。

http://localhost:3000/users/sign_in
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/b5070d8c-a272-d31f-8d28-a977146dde7b.png)



http://localhost:3000/users/password/new
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/8ad9deff-98a9-5672-d16c-60c2ec7b542f.png)


<br>
##6. 日本語に変更（localeの日本語化）
デフォルトの言語は英語なので日本語に変更する。（deviseではなくrailsアプリケーションの言語設定を変える）

###6-1. configファイルの変更
config/application.rb に以下を追記。

```rb:application.rb
config.i18n.default_locale = :ja
```

<br>
###6-2. localesディレクトリに言語ファイルを追加
config/locales配下に以下２つのファイルを追加する。

・ja.yml
・devise.ja.yml

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/89dc8254-1f19-9b6a-b236-ebeeb652d330.png)


（参考）[i18nとは？使い方まとめ。ビュー(View),コントローラー(Controller), コンソールでの呼び出し方とlocaleの使い方](https://qiita.com/yuta-38/items/0d24dca97f650aa2f902)




```yml:devise.ja.yml
ja:
  errors:
    messages:
      not_found: "は見つかりませんでした"
#      not_found: "not found"
      already_confirmed: "は既に登録済みです"
#      already_confirmed: "was already confirmed"
      not_locked: "は凍結されていません"
#      not_locked: "was not locked"

  devise:
    failure:
      unauthenticated: 'ログインしてください。'
#      unauthenticated: 'You need to sign in or sign up before continuing.'
      unconfirmed: '本登録を行ってください。'
#      unconfirmed: 'You have to confirm your account before continuing.'
      locked: 'あなたのアカウントは凍結されています。'
#      locked: 'Your account is locked.'
      invalid: 'メールアドレスかパスワードが違います。'
#      invalid: 'Invalid email or password.'
      invalid_token: '認証キーが不正です。'
#      invalid_token: 'Invalid authentication token.'
      timeout: 'セッションがタイムアウトしました。もう一度ログインしてください。'
#      timeout: 'Your session expired, please sign in again to continue.'
      inactive: 'アカウントがアクティベートされていません。'
#      inactive: 'Your account was not activated yet.'
    sessions:
      signed_in: 'ログインしました。'
#      signed_in: 'Signed in successfully.'
      signed_out: 'ログアウトしました。'
#      signed_out: 'Signed out successfully.'
    passwords:
      send_instructions: 'パスワードのリセット方法を数分以内にメールでご連絡します。'
#      send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
      updated: 'パスワードを変更しました。'
#      updated: 'Your password was changed successfully. You are now signed in.'
    confirmations:
      send_instructions: '登録方法を数分以内にメールでご連絡します。'
#      send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
      confirmed: 'アカウントを登録しました。'
#      confirmed: 'Your account was successfully confirmed. You are now signed in.'
    registrations:
      signed_up: 'アカウント登録を受け付けました。確認のメールをお送りします。'
#      signed_up: 'You have signed up successfully. If enabled, a confirmation was sent to your e-mail.'
      updated: 'アカウントを更新しました。'
#      updated: 'You updated your account successfully.'
      destroyed: 'アカウントを削除しました。またのご利用をお待ちしております。'
#      destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
    unlocks:
      send_instructions: 'アカウントの凍結解除方法を数分以内にメールでご連絡します。'
#      send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
      unlocked: 'アカウントを凍結解除しました。'
#      unlocked: 'Your account was successfully unlocked. You are now signed in.'
    mailer:
      confirmation_instructions:
        subject: 'アカウントの登録方法'
#        subject: 'Confirmation instructions'
      reset_password_instructions:
        subject: 'パスワードの再設定'
#        subject: 'Reset password instructions'
      unlock_instructions:
        subject: 'アカウントの凍結解除'
#        subject: 'Unlock Instructions'
```


```yml:ja.yml
ja:
  activerecord:
    attributes:
      user:
        confirmation_sent_at: パスワード確認送信時刻
        confirmation_token: パスワード確認用トークン
        confirmed_at: パスワード確認時刻
        created_at: 作成日
        current_password: 現在のパスワード
        current_sign_in_at: 現在のログイン時刻
        current_sign_in_ip: 現在のログインIPアドレス
        email: Eメール
        encrypted_password: 暗号化パスワード
        failed_attempts: 失敗したログイン試行回数
        last_sign_in_at: 最終ログイン時刻
        last_sign_in_ip: 最終ログインIPアドレス
        locked_at: ロック時刻
        password: パスワード
        password_confirmation: パスワード（確認用）
        remember_created_at: ログイン記憶時刻
        remember_me: ログインを記憶する
        reset_password_sent_at: パスワードリセット送信時刻
        reset_password_token: パスワードリセット用トークン
        sign_in_count: ログイン回数
        unconfirmed_email: 未確認Eメール
        unlock_token: ロック解除用トークン
        updated_at: 更新日
    models:
      user: ユーザー
  devise:
    confirmations:
      confirmed: メールアドレスが確認できました。
      new:
        resend_confirmation_instructions: アカウント確認メール再送
      send_instructions: アカウントの有効化について数分以内にメールでご連絡します。
      send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。
    failure:
      already_authenticated: すでにログインしています。
      inactive: アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。
      invalid: "%{authentication_keys}またはパスワードが違います。"
      last_attempt: もう一回誤るとアカウントがロックされます。
      locked: アカウントはロックされています。
      not_found_in_database: "%{authentication_keys}またはパスワードが違います。"
      timeout: セッションがタイムアウトしました。もう一度ログインしてください。
      unauthenticated: ログインもしくはアカウント登録してください。
      unconfirmed: メールアドレスの本人確認が必要です。
    mailer:
      confirmation_instructions:
        action: メールアドレスの確認
        greeting: "%{recipient}様"
        instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。
        subject: メールアドレス確認メール
      email_changed:
        greeting: こんにちは、%{recipient}様。
        message: メールアドレスの（%{email}）変更が完了したため、メールを送信しています。
        message_unconfirmed: メールアドレスが（%{email}）変更されたため、メールを送信しています。
        subject: メール変更完了
      password_change:
        greeting: "%{recipient}様"
        message: パスワードが再設定されました。
        subject: パスワードの変更について
      reset_password_instructions:
        action: パスワード変更
        greeting: "%{recipient}様"
        instruction: パスワード再設定の依頼を受けたため、メールを送信しています。下のリンクからパスワードの再設定ができます。
        instruction_2: パスワード再設定の依頼をしていない場合、このメールを無視してください。
        instruction_3: パスワードの再設定は、上のリンクから新しいパスワードを登録するまで完了しません。
        subject: パスワードの再設定について
      unlock_instructions:
        action: アカウントのロック解除
        greeting: "%{recipient}様"
        instruction: アカウントのロックを解除するには下のリンクをクリックしてください。
        message: ログイン失敗が繰り返されたため、アカウントはロックされています。
        subject: アカウントのロック解除について
    omniauth_callbacks:
      failure: "%{kind} アカウントによる認証に失敗しました。理由：（%{reason}）"
      success: "%{kind} アカウントによる認証に成功しました。"
    passwords:
      edit:
        change_my_password: パスワードを変更する
        change_your_password: パスワードを変更
        confirm_new_password: 確認用新しいパスワード
        new_password: 新しいパスワード
      new:
        forgot_your_password: パスワードを忘れましたか？
        send_me_reset_password_instructions: パスワードの再設定方法を送信する
      no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。
      send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。
      send_paranoid_instructions: メールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。
      updated: パスワードが正しく変更されました。
      updated_not_active: パスワードが正しく変更されました。
    registrations:
      destroyed: アカウントを削除しました。またのご利用をお待ちしております。
      edit:
        are_you_sure: 本当によろしいですか？
        cancel_my_account: アカウント削除
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: 空欄のままなら変更しません
        title: "%{resource}編集"
        unhappy: 気に入りません
        update: 更新
        we_need_your_current_password_to_confirm_your_changes: 変更を反映するには現在のパスワードを入力してください
      new:
        sign_up: アカウント登録
      signed_up: アカウント登録が完了しました。
      signed_up_but_inactive: ログインするためには、アカウントを有効化してください。
      signed_up_but_locked: アカウントがロックされているためログインできません。
      signed_up_but_unconfirmed: 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。
      update_needs_confirmation: アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。
      updated: アカウント情報を変更しました。
      updated_but_not_signed_in: あなたのアカウントは正常に更新されましたが、パスワードが変更されたため、再度ログインしてください。
    sessions:
      already_signed_out: 既にログアウト済みです。
      new:
        sign_in: ログイン
      signed_in: ログインしました。
      signed_out: ログアウトしました。
    shared:
      links:
        back: 戻る
        didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか？
        didn_t_receive_unlock_instructions: アカウントのロック解除方法のメールを受け取っていませんか？
        forgot_your_password: パスワードを忘れましたか？
        sign_in: ログイン
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: アカウント登録
      minimum_password_length: "（%{count}字以上）"
    unlocks:
      new:
        resend_unlock_instructions: アカウントのロック解除方法を再送する
      send_instructions: アカウントのロック解除方法を数分以内にメールでご連絡します。
      send_paranoid_instructions: アカウントが見つかった場合、アカウントのロック解除方法を数分以内にメールでご連絡します。
      unlocked: アカウントをロック解除しました。
  errors:
    messages:
      already_confirmed: は既に登録済みです。ログインしてください。
      confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。
      expired: の有効期限が切れました。新しくリクエストしてください。
      not_found: は見つかりませんでした。
      not_locked: はロックされていません。
      not_saved:
        one: エラーが発生したため %{resource} は保存されませんでした。
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"
```

https://gist.github.com/yhara/606476
https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml

<br>
###ページをリロード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/9ed77a63-6b99-5018-ddca-bb1caa2478a0.png)

一部の英語が日本語に変換された。残りはビューを変更する必要がある。


<br>
##7. ビューの日本語化
dockerコンテナ内で`rails g devise:views`を実行する。


```
/app # rails g devise:views
Running via Spring preloader in process 564
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/e917e6a3-d43a-b156-551d-234248c1c39b.png)

viewsディレクトリ配下にdeviseのディレクトリが追加された。


| ディレクトリ | 内容 | ファイル |
|:-:|:--|:--|
| confirmation  | パスワード再発行用のフォーム  | new.html.erb  |
| mailer  | 送信するメールの内容  | (1)confirmation_instructions.html.erb, (2)email_changed.html.erb, (3)password_change.html.erb, (4)reset_password_instructions.html.erb, (5)unlock_instructions.html.erb|
| passwords  | パスワードを忘れた時のフォーム  | (1)edit.html.erb, (2)new.html.erb  |
| registrations  | ユーザー情報の新規登録と編集フォーム  |   | (1)edit.html.erb, (2)new.html.erb
| sessions  | ログインフォーム  | new.html.erb  |
| shared  | 共通のエラーメッセージとリンク部分のテンプレート  |   |(1)_error_messages.html.erb, (2)_links.html.erb
| unlocks  | アカウントロック通知メールのフォーム  |  new.html.erb |


<br>
http://localhost:3000/users/sign_in　で表示されるビューを編集する。

app/views/devise/sessions/new.html.erb

```erb:new.html.erb
<h2>ログイン</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password" %>
  </div>

  <% if devise_mapping.rememberable? %>
    <div class="field">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit "送信" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```

<br>
下部の`<%= render "devise/shared/links" %>`は以下のファイルを呼び出しているため、該当部分を変更する。

app/views/devise/shared/_links.html.erb


```erb:_links.html.erb
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "新規登録", new_registration_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "パスワードを忘れましたか?", new_password_path(resource_name) %><br />
<% end %>
```

<br>
**▼ページをリロード**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c2861f70-e381-fd15-bf95-b24215c05232.png)

これで日本語化が完了。


<br>
##8. フラッシュメッセージの作成
deviseをインストールしたときの指示にしたがってapplication.html.erbにフラッシュメッセージを表示するタグを追加する。

```:deviseの説明
  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>
```

`notice`はログインや投稿に成功した時に表示されるメッセージ。
`alert`はEメールやPWが正しくない場合に表示されるメッセージ。

app/views/layouts/application.html.erb のbodyタグにフラッシュメッセージを表示するタグを追加。


```erb:application.html.erb
  <body>
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>
  </body>
```

![flash.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/6e8c3a39-ae9b-2133-ae27-11d2400c82a7.gif)

画面上部にフラッシュメッセージが表示されるようになった。

ただし、この状態ではデフォルト状態で隙間が開いてしまうためフラッシュメッセージが存在する時のみタグを表示するように変更する。

<br>

```erb:application.html.erb
  <body>
    <% if notice %>
　  　<p><%= notice %></p>
    <% end %>
    <% if alert %>
      <p><%= alert %></p>
    <% end %>

    <%= yield %>
  </body>
```

<br>
![flash2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c2b2aa47-6127-9240-536d-49a84ae1e0f4.gif)


<br>
##9. ログイン後に表示されるページを変更する
ログインに成功するとデフォルトでroot_urlにリダイレクトされる。これを任意のページに変更する。

###9-1. rails g controller コントローラ名
例として、pages > show.html.erbを表示する設定にする。

Dockerコンテナの中に入りコントローラーを作成する。

```
$ docker-compose exec サービス名 sh
/app # rails g controller pages
```


###9-2. ビューの作成
生成されたapp/views/pages配下に show.html.erb を作成する。

```erb:app/views/pages/show.html.erb
<div>ログイン成功後に表示されるページ</div>

<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
```

###9-3. ルーティングの設定
ルート名を`pages_show`とする。

```rb:routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'top#index'

  #追記
  get '/pages/show', to: 'pages#show', as: :pages_show
end
```

・`get 'URI', to: 'コントローラ名#アクション名, as: :ルート名'`


<br>
###9-4. コントローラの設定（application_controller.rb）
ログインに成功した場合と失敗した場合に表示するビューへのリダイレクトを設定する。

使うルート名は`pages_show`と`new_user_session`。詳細は`rails routes`で確認できる。

```
/app # rails routes
             Prefix Verb   URI Pattern                                                                                       Controller#Action
    new_user_session GET    /users/sign_in(.:format)                                                                          devise/sessions#new
          pages_show GET    /pages/show(.:format)                                                                             pages#show
```

new_user_sessionはログインページ。


<br>

app/controllers/application_controller.rbに以下のようにメソッドを記述する。

applcation_controller.rbのメソッドは他のコントローラにも読み込まれる（`<`で拡張しているため）。


```rb:application_controller.rb
class ApplicationController < ActionController::Base
  def after_sign_in_path_for(resource)
    pages_show_path
  end

  private
      def sign_in_required
          redirect_to new_user_session_url unless user_signed_in?
      end
end
```


####Deviseのログイン後にリンクするページ
`after_sign_in_path_for`ヘルパをで指定する。

```rb:ルートへのパス
  def after_sign_in_path_for(resource)
    パス
  end
```

`ルート名_path`は指定したルートのパス（'/配下'）を表示するヘルパ。

`pages_show`を指定した場合、対応するpagesコントローラのshowメソッドが実行される（`pages#show`）


####ログインに失敗した場合の処理
ログインに失敗した場合はnew_user_session

```rb:
  private
      def sign_in_required
          redirect_to new_user_session_url unless user_signed_in?
      end
```

このメソッド名はなんでもいい。pagesコントローラで使う時に合わせる。

`private`を記述すると、同じクラス（拡張したクラスも含む）の中からしか読み込めないようにできる。

・unless修飾子を使った条件分岐
`trueで実行する処理 unless 条件`

user_signed_inが存在すればnew_user_sessionのルートにリダイレクトする。

・`redirect_to URL`
指定したURLにリダイレクトする

・`ルート名_url`
指定したルート名のurlを呼び出す。

・`unless 条件`
ifの逆。条件がfalseだった時に指定した処理を実行する。`if !条件`と同じ。

・`user_signed_in?`
Deviseのヘルパ。ログインしている場合はtureになる。（便利！）



<br>
###9-5. コントローラの設定（pages_controller.rb）
ログインに成功した時に表示するapp/controllers/pages_controller.rbの設定をする。

`before_action`を使って先ほど定義した`is_signed_in`メソッドを呼び出してから、showアクションを実行する。

```rb:pages_controller.rb
class PagesController < ApplicationController
  before_action :is_signed_in, only: [:show]

  def show
  end
end
```

・`before_action :メソッド名`
アクションを指定しない場合は、すべてのアクションの前に指定したメソッドが実行される。


・`before_action :メソッド名, only: [:アクション名, アクション名,,,]`

only: を使うと指定したアクションを実行する場合のみメソッドを実行する。

・`before_action :メソッド名, except: [:アクション名, アクション名,,,]`

exceptを使うと指定したアクション以外でメソッドを実行する。

>onlyもexceptもアクションが一つの場合は`[ ]`がなくてもいい。


####補足：別の記述方法
deviseのヘルパで`authenticate_user!`を使うとより簡単に記述できる。これは、ログインしてない場合はログインページにリダイレクトするヘルパ。

```rb:
class PagesController < ApplicationController
  before_action :authenticate_user!, only: [:show]

  def show
  end
end
```

↓↑　同じ

```rb:
class PagesController < ApplicationController
  before_action :is_signed_in, only: [:show]

  def show
  end
end
  
  private
      def is_signed_in
          redirect_to new_user_session_url unless user_signed_in?
      end
```



###9-6. ブラウザの表示
ログイン画面`/users/sign_in`から正しくログインできると指定したページに飛ぶことができる。

![login.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/1c7c003c-620f-7e9d-8ac4-098fbef05b4b.gif)


<br>
##10. Deviseのヘルパ
モデル名をuserとして生成した場合（`rails g devise user`）の例。

別のモデル名を使った場合は、userの部分を指定したモデル名に変更する。


###10-1. `before_action  ：authenticate_user！`
ログインしてない場合はログインページにリダイレクトするヘルパ。コントローラの上部で使う。

```rb:
class PagesController < ApplicationController
  before_action :authenticate_user!, only: [:show]

  def show
  end
end
```


###10-2. `user_signed_in？`
ユーザーがログインしている場合はtrueを返す。

```rb:デバッグ例
  def show
    pp "user_signed_in?:#{user_signed_in?}"
  end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/f290a1ff-eb84-12e9-2013-83d3c3ac44da.png)




###10-3. `current_user`
現在のセッションのユーザー情報を取得できる。保存してある情報は、４つ。(1)id, (2)email, (3)created_at, (4)updated_at。

```
irb(main):009:0> User.find[1]
  User Load (0.8ms)  SELECT "users".* FROM "users"
=> #<User id: 2, email: "111@222", created_at: "2021-04-15 05:08:07.005182000 +0000", updated_at: "2021-04-15 05:08:07.005182000 +0000">
```

ユーザーのidを呼び出したい場合は、`current_user.id`とする。

```rb:デバッグ例
  def show
    pp "current_user:#{current_user}"
    pp "current_user.id:#{current_user.id}"
  end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/e15642ac-823b-19de-5531-7d53f6d27e83.png)



###10-4. `user_session`
ユーザーのセッション情報を格納しておく変数。デフォルトは空。

```rb:デバッグ例
  def show
    pp "user_session:#{user_session}"
    user_session["point"] = 5432
    user_session["os"] = ["apple", "windows"]
    pp "user_session:#{user_session}"
  end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/eec86cd3-93da-852f-ef31-14106b0029d3.png)

一度ログアウトしてから、再度ログインすると空になる。


<br>
##11. 新規登録時のメール承認機能の実装
`confirmable`モジュールを使うとユーザーの新規登録時にメール承認機能をつけることができる。

###11-1. モデルに`confirmable`を追加
app/models/user.rb

```rb:user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable
end
```

<br>
###11-2. メール送信用サーバーの設定

config/environments/development.rb に以下を追記。

```rb:development.rb
Rails.application.configure do
  (省略)

  # mailer setting
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => 'smtp.gmail.com',
    :port => 587,
    :user_name => 'gmailのアドレス',
    :password => 'gmailのアプリパスワード',
    :authentication => :plain,
    :enable_starttls_auto => true
  }

  (省略)
end
```


###11-3. Deviseのメーラー設定
次に、Deviseのconfigファイルでメーラーを指定する。
config/initializers/devise.rb

```rb:devise.rb
config.mailer_sender = 'gmailのアドレス'
```


<br>
###11-4. reconfirmableをfalseにする
メールアドレス更新時にメール確認を必須にするreconfirmableをfalseにする。（デフォルトはtrue）

config/initializers/devise.rb

```rb:devise.rb
  # If true, requires any email changes to be confirmed (exactly the same way as
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
  # db field (see migrations). Until confirmed, new email is stored in
  # unconfirmed_email column, and copied to email column on successful confirmation.
  config.reconfirmable = false
```


<br>
###11-5. アプリパスワードの発行
gmailで自動のアプリケーションを使ってメール送信する場合、PWにはアプリパスワードを指定する必要がある。

ログインしてセキュリティを開く。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/7d1f4363-7c00-e136-422e-f67196cfea07.png)

↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/ecaa4ed1-3a9d-9ea7-903e-fa10f44b10f3.png)

↓ 「その他」を選択し、名前を適当に入力。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/b39069bd-199e-038f-8001-c29737f56ff7.png)

表示されたパスワードをコピーして、`:password => 'gmailのアプリパスワード',`にペーストする。


これで、現在のrailsアプリケーションで指定したメールサーバーが使えるようになった。




<br>
###11-6. マイグレーションファイルのconfirmableに必要なカラムを有効化する

db/migrate/yyyymmdd_devise_create_users.rb

confirmableのコメントアウトを外す。

```rb:yyyymmdd_devise_create_users.rb
      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable
```

アプリケーションを`ctrl + c`で終了してから再起動し、`rails db:migrate`を実行する。


```
#アプリケーションの起動（プロジェクトのルートディレクトリで）
$ docker-compsoe up

#コンテナ内に入る
$ docker exec -it コンテナ名 sh

#マイグレーション
$ rails db:migrate
```

<br>
###11-7. ブラウザで検証
新規登録時のメール確認なので、`/users/sign_up`にアクセスする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/db72f555-2a3a-1587-aaea-106f16302c49.png)

「Sign up」をクリックするとTOPページにリダイレクトし、上部に「
本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。」が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/8b8f9b47-243c-fd0a-3dc7-7d3f835030ed.png)

```:メール本文
Welcome example@com!

You can confirm your account email through the link below:

Confirm my account
```

ログイン画面に飛び、「リンクをクリックするとメールアドレスが確認できました」が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/50af3ef1-8ab5-8974-9815-19ba14543158.png)

登録したEメールとパスワードを入力すればログインが完了する。

localhostのポート番号が異なる場合はconfig/environments/development.rbで設定。



####link_toで生成されるリンク

```
<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
```

confirmation_urlヘルパに`@resource`と`@token`の２つの変数を渡している。

http://localhost:3000/users/confirmation?confirmation_token=WxY92fWYymUcDt8Ss-6S



<br>
##11-8. confirmableの送信メールの内容を変更する
初回登録時に送信されるメール内容は以下ファイルに記述されている。
app/views/devise/mailer/confirmation_instructions.html.erb

```erb:confirmation_instructions.html.erb

<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
```

↓

```erb:confirmation_instructions.html.erb

<p>ご登録ありがとうございます。</p>
<p>登録されたメールアドレスは<%= @email %>です。</p>

<p>下記リンクをクリックして最終承認を完了させてください。</p>

<p><%= link_to '「ご登録の承認」', confirmation_url(@resource, confirmation_token: @token) %></p>
```