初心者向け devise導入について

# 記事にした経緯
私が作成中のアプリにログイン機能を実装するため、`devise`を実装しようとした時に色々な課題があったため忘備録として記事にしました。
既存のusersテーブル
|カラム名|データ型|備考|
|:-:|:-:|:-:|
|nickname|string|Notnull制約、presenceバリデーションあり|
|gender|integer|Notnull制約、presenceバリデーションあり|
|age|integer|Notnull制約、presenceバリデーションあり|


アプリを作成し`devise`をインストールします。

```:ターミナル
rails new devise_sumple -d postgreSQL (アプリ作成)
　　　　　　↓
rails db:create (データベース作成)
```
```:Gemfile
gem 'devise'
# ***** 以上を追加 *****
group :development, :test do
  # 略
```
```:ターミナル
bundle install　　　　（gemをインストールする）
   ↓
rails g devise:install　（アプリにdeviseをインストール）
   ↓
rails g devise User　（deviseで認証するためのモデルを作成）
```
以下のファイルやコードが作成される
```:ターミナル
Running via Spring preloader in process 19712
      invoke  active_record
      create    db/migrate/20220716085821_devise_create_users.rb
      create    app/models/user.rb
      insert    app/models/user.rb
       route  devise_for :users
```
先ほど作成したマイグレーションファイル(devise_create_users.rb)に必要があれば修正を加えていく。
```rb:マイグレーションファイル
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
```
公式ページを見てみる

https://github.com/heartcombo/devise

それぞれのカラムについての説明を上記コードに追加しておいた。
各自で不要なカラムはコメントアウトしてほしい。
私はパスワードのリセットする設定は不要なので`Recoverable：`のカラムと関係しているコードをコメントアウト、モデルの`:recoverable,`を削除した。

```rb:マイグレーションファイル
          #~省略
 ## Recoverable：パスワードをリセットするためのカラム
     #t.string   :reset_password_token
     #t.datetime :reset_password_sent_at
          #~省略
 #add_index :users, :reset_password_token, unique: true
```

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

これでマイグレーションファイルやUserモデルの編集が完了したため`マイグレーション`を実行する。
```:ターミナル
 rails db:migrate 
　　　↓上手くいくと下記が表示される

Running via Spring preloader in process 22019
== 20220716085821 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0460s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0030s
== 20220716085821 DeviseCreateUsers: migrated (0.0492s) =======================
```
この状態でログイン画面にアクセスして動作を確認しようとするとある問題が起こる
ログイン画面へアクセス→http://localhost:3000/users/sign_in 
サインアップ画面に遷移する
以下のテストデータでサインアップしようとするとエラーが発生する
|テスト用email|テスト用パスワード|
|:-:|:-:|
| test@example.com | password |
```:エラー
1 error prohibited this user from being saved:
Nickname can't be blank
```
`nickname`カラムが空白で保存することを禁止したと表示される。
原因は既存のUserモデルで`nickname``gender``age`に`presenceバリデーション`をかけていることだ。
なので`nickname``gender``age`カラムが空白にならないようにdeviseのviewファイルを編集していく。
その前にまずは、`application.html.erb`にサインイン状態と、サインアウト状態でheaderの表示が変化するよう修正していく。

```erb:app/views/layouts/application.html.erb
 <!-- 略 -->
  <body>
    <!--以下を追加-->
    <header>
      <%= render "layouts/header" %>
    </header>
    <hr>
    <!--以上を追加-->
    <%= yield %>
  </body>
  <!-- 略 -->
```
ターミナルで`_header.html.erb`を作成
```:ターミナル
touch app/views/layouts/_header.html.erb
```
作成した新規ファイルにサインインしている時、そうでない時の`条件分岐`で表示を変えるよう修正していく
```erb:app/views/layouts/_header.html.erb
<% if user_signed_in? %>
  <%# ログイン時 %>
  <%= link_to "アカウント編集", edit_user_registration_path %>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete, data: { confirm: "ログアウトしますか？" } %>
  <%= "【ログイン中のアドレス】#{current_user.email}" %>
<% else %>
  <%# 非ログイン時 %>
  <%= link_to "新規登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>
```

### 解説
_Q:`user_signed_in?`とは_
`devise`が用意している`メソッド`で、`ユーザー`が`サインイン`しているかを確かめるための`メソッド`。
サインイン　　→   `ture`
ログアウト　　→   `false`
つまり上記のコード内容は

サインインしている？
tureの時は
アカウント編集　と　ログアウト　と　ログイン中のアドレス　を表示
そうでない時（ログアウト）は
新規登録
ログイン
を表示
と表わしている。

このままでは英語で分かりづらいので`日本語化`を実装していく
## まずは多言語対応設定
`Gemfile`に以下のgemを追加する

```rb:Gemfile
# ログイン機能
gem 'devise'

# 以下を追加 
# 多言語対応(Rails 6の場合)
gem 'rails-i18n', '~> 6.0'
gem 'devise-i18n'
# 以上を追加 
group :development, :test do
  # 略
```
新しくgemを追加したので正常に動作するよ依存関係を整える
```:ターミナル
bundle install
```
最後に`config/application.rb`へ`日本語対応`を設定
```rb:config/application.rb
module LikeApp
  class Application < Rails::Application
    config.load_defaults 6.0
    #  以下を追加 
    config.i18n.default_locale = :ja
    config.time_zone = 'Asia/Tokyo'
    #  以上を追加 
    # 略
```
これで一旦、`日本語化`の実装が完了。サインイン画面で日本語化されているか確認 
http://localhost:3000/users/sign_in 

# usersテーブルにカラムを追加したい
テーブルにカラムを追加する時は、既存のマイグレーションファイルに直接修正を加えるのではなく、新しくマイグレーションファイルを作成しマイグレーションを実行する
ますは新たなマイグレーションファイルを作成する
```:ターミナル
rails g migration クラス名　　　（AddColumnUsers ← Add_Column_追加先のテーブル名が一般的らしい）

　　　　　　↓（新しいマイグレーションファイルが作成される）

Running via Spring preloader in process 31808
      invoke  active_record
      create    db/migrate/20220717032330_add_column_users.rb　　
```
```rb:新しく作成されたマイグレーションファイル
class AddColumnUsers < ActiveRecord::Migration[6.1]

  def change
  end

end
```
`AddColumnUsers`というクラスの中に`change`というメソッドがある。
この`change`というメソッドの中に追加したいカラムを追記していく

私が今回作成したいUsersテーブルの構造は以下である。
| カラム名 | データ型 | 備考 |
|:-:|:-:|:-:|
| email     | string   | deviseがデフォルトで作成済み |
| password  | string   | deviseがデフォルトで作成済み |
| nickname  | string   | new! 新しく追加したい  |
| gender    | integer  | new! 新しく追加したい (enumで実装したい)|
| age       | integer  | new! 新しく追加したい (enumで実装したい)|

2022/07/18 **`enum`については別の記事にする予定です**
とりあえずstringでないことは一旦おいておいて進みます
以下の様に追記する
```rb:新規マイグレーションファイル
class AddColumnUsers < ActiveRecord::Migration[6.1]

  def change
    #カラムを追加するメソッド:追加先のテーブル名, :カラム名, :データ型
    add_column :users, :nickname, :string
    add_column :users, :gender, :integer
    add_column :users, :nickname, :integer
  end

end
```
データベースに反映するために以下のコマンドを実施
```:ターミナル
rails db:migrate
　　↓成功すると以下が表示される

Running via Spring preloader in process 33398
== 20220717032330 AddColumnUsers: migrating ===================================
-- add_column(:users, :nickname, :string)
   -> 0.0025s
-- add_column(:users, :gender, :integer)
   -> 0.0014s
-- add_column(:users, :age, :integer)
   -> 0.0012s
== 20220717032330 AddColumnUsers: migrated (0.0052s) ==========================
```
データベースの更新をしたので`schemaファイル`の中身を確認する。
```rb:schema.rb   
    ~省略~    
    create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "nickname"
    t.integer "gender"
    t.integer "age"
    t.index ["email"], name: "index_users_on_email", unique: true
     ~省略~
```

よく見ると、`nickename`、`gender`、`age`に`制約`をかけていない、モデルにもバリデーションをかけいないので制約とバリデーションをかける作業を行なっていく。
Userモデルに空文字を防ぐバリデーションを追記。
```rb:models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :rememberable, :validatable
         with_options presence: true do
          validates :nickname
          validates :gender
          validates :age
        end
end
```
新たに制約を追加するために新規マイグレーションファイルを作成
```:ターミナル
rails g migration ChangeColumnsAddNotnullOnUsers
```
作成されたマイグレーションファイルに``メソッドを使ってNotNull制約を追加
```rb:新規マイグレーションファイル
class ChangeColumnsAddNotnullOnUsers < ActiveRecord::Migration[6.1]
  def change
    change_column :users, :nickname, :string, null: false
    change_column :users, :gender, :integer, null: false
    change_column :users, :age, :integer, null: false
  end
end
```
モデルにバリデーションをかけ、マイグレーションファイルには制約を追記した。
この状態でサインインや新規登録(`性別:0` `年齢:28`と入力)をしようとするとある問題が発生する　 
http://localhost:3000/users/sign_in 
追加したカラム `nickename`、`gender`、`age`　に入力がないとエラーが起きてしまう。バリデーションやNotNull制約が作動しているためである。
なので新規登録画面に`nickename`、`gender`、`age`の追加をする。
まずはdeviseのviewを生成する。
```:ターミナル
rails g devise:i18n:views

以下↓生成されるファイル
 invoke  Devise::I18n::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  Devise::I18n::MailerViewsGenerator
      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
      invoke  i18n: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
```
生成されたファイルの名前や中身をよく見てみると、エラーメッセージ系、メアドやパスワードを変更した時のメールでのお知らせ系などのviewファイルが揃っている。今回は追加したカラムに応じた入力フォームを作成したいので`app/views/devise/registrations/new.html.erb`に変更を加えていく。

```erb:app/views/devise/registrations/new.html.erb
<h2><%= t('.sign_up') %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>
 
　　　　　　　　　　　　　　　　　<!--下記追加-->

  <div class="field">
    <%= f.label :nickname, "ニックネーム" %><br />
    <%= f.text_field :nickname, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :gender, "性別" %><br />
    <%= f.text_field :gender, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :age, "年齢" %><br />
    <%= f.text_field :age, autofocus: true %>
  </div>

　　　　　　　　　　　　　　　<!--上記追加-->

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

 　　　　　　　　　　　　　　　#省略

<%= render "devise/shared/links" %>
```
この状態で http://localhost:3000/users/sign_up にアクセスしフォームに入力してもまだエラーが表示されます。
ターミナルでエラーログを見てみると`nickename`、`gender`、`age`が許可されていないと表示されています。
```:ターミナル
# 省略
Unpermitted parameters: :nickname, :gender, :age
# 省略
```
どうやら勝手に作成したカラムに勝手にパラメーターを保存できないようにストロングパラメーターが起動した様子です。
解決方法について公式サイトのREADMEに記載されていました。https://github.com/heartcombo/devise#strong-parameters

こちらを参考に`app/controllers/application_controller.rb`を修正してみます。
```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
    before_action :configure_permitted_parameters, if: :devise_controller?

    def configure_permitted_parameters
      devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :gender, :age])
      devise_parameter_sanitizer.permit(:account_update, keys: [:nickname, :gender, :age])
    end
end
```
もう一度へアクセスして、各入力フォームに入力してみる（`性別:0` `年齢:28`と入力）
`Yay! You’re on Rails!`と表示されたら
deviseにカラムの追加、入力フォームの追加が成功したということになる。
筆者はここから性別と年齢の入力をenumを使って実装してくが、とりあえずdeviseを使ってログイン機能の実装はここで一旦終了とする。
2022年7月17日　執筆中