Deviseで複数モデルを使用したログインの実装

## やりたいこと
親ユーザーの認証ではデフォルトのemailとpasswordでの認証を使用してログイン。
子ユーザーの認証では、nicknameとパスワードで登録と認証ができるようにする。
また、子ユーザーを作成できるのは、親ユーザーのみ。
family（親ユーザー）
|colum|validation|
|:--|:--:|
|email|null:false|
|password|null:false|
|family_name|null:false|

user(子ユーザ）
|colum|validation|
|:--|:--:|
|nickname|null:false|
|password|null:false|
|family_id|null:false|

## 手順
#### root用のコントロ3ーラーとビューを作成
```rails g controller home index```
```ruby:home_contoroller.rb
class HomeController < ApplicationController
  def index
  end
end
```
#### rootを変更
作成したhome/indexをrootに変更。
```ruby:routes.rb
Rails.application.routes.draw do
  get 'home/index'
end
```
```rails s```を実行してから```localhost:3000```にアクセスして表示を確認する。

#### gemのインストール
Gemfileに追記
```ruby:Gemfile
gem 'devise'
```
deviseをインストール

```bundle install```
```bundle exec rails g devise:install``` 忘れがちなので注意
#### deviseの設定変更

```ruby:initializers/devise.rb（変更前）
Devise.setup do |config|

#省略
 # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  # config.scoped_views = true ←このコメントアウトをはずす
```
```ruby:initializers/devise.rb（変更後）
Devise.setup do |config|

#省略
 # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  config.scoped_views = true ←このコメントアウトをはずす
```
上記はスコープ付きビューが必要な場合に必要です。今回は結局いらなかったかも？
[公式リファレンス](https://github.com/heartcombo/devise/wiki/How-to-Setup-Multiple-Devise-User-Models#3-if-you-want-scoped-views)

#### deviseのモデルの作成

以下のコマンドを実行してdeviseのmodelを作成。
```rails g devise family```
```rails g devise user```
作成できたらcolumの追加をする。

```ruby:db/migrate/****_devise_create_family.rb
class DeviseCreateFamilies < ActiveRecord::Migration[6.0]
  def change
    create_table :families do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :family_name,        null: false ←ここを追加

#省略
```

```ruby:db/migrate/****_devise_create_user.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :nickname,           null: false, default: ""
      t.integer :family_id, null: false

# 省略
```
family_idは最初reference型で参照していたが、うまくいかなかったのでintegerにして直接数値を持ってくることにした。
migratetionファイルの編集が完了したら忘れずにマイグレーションを行う。
```rails db:migrate```
```rails db:migrate:status```でmigrateできているかの確認をおこなう。

#### ルーティングの修正

ルーティングを変更しないと
変更しないと、familyとuserの使うコントローラーがぶつかってしまうので以下に変更。

```ruby:routes.rb
Rails.application.routes.draw do

  get 'home/index'
  
  devise_for :families, controllers: {
    sessions:      'families/sessions',
    passwords:     'families/passwords',
    registrations: 'families/registrations'
  }
  devise_for :users, controllers: {
    sessions:      'users/sessions',
    passwords:     'users/passwords',
    registrations: 'users/registrations'
  }
root to: 'home/index'
end
```
これでルーティングが以下のようになる

```
                               Prefix Verb   URI Pattern                                                                              Controller#Action
                           home_index GET    /home/index(.:format)                                                                    home#index
                   new_family_session GET    /families/sign_in(.:format)                                                              families/sessions#new
                       family_session POST   /families/sign_in(.:format)                                                              families/sessions#create
               destroy_family_session DELETE /families/sign_out(.:format)                                                             families/sessions#destroy
                  new_family_password GET    /families/password/new(.:format)                                                         families/passwords#new
                 edit_family_password GET    /families/password/edit(.:format)                                                        families/passwords#edit
                      family_password PATCH  /families/password(.:format)                                                             families/passwords#update
                                      PUT    /families/password(.:format)                                                             families/passwords#update
                                      POST   /families/password(.:format)                                                             families/passwords#create
           cancel_family_registration GET    /families/cancel(.:format)                                                               families/registrations#cancel
              new_family_registration GET    /families/sign_up(.:format)                                                              families/registrations#new
             edit_family_registration GET    /families/edit(.:format)                                                                 families/registrations#edit
                  family_registration PATCH  /families(.:format)                                                                      families/registrations#update
                                      PUT    /families(.:format)                                                                      families/registrations#update
                                      DELETE /families(.:format)                                                                      families/registrations#destroy
                                      POST   /families(.:format)                                                                      families/registrations#create
                     new_user_session GET    /users/sign_in(.:format)                                                                 users/sessions#new
                         user_session POST   /users/sign_in(.:format)                                                                 users/sessions#create
                 destroy_user_session DELETE /users/sign_out(.:format)                                                                users/sessions#destroy
                    new_user_password GET    /users/password/new(.:format)                                                            users/passwords#new
                   edit_user_password GET    /users/password/edit(.:format)                                                           users/passwords#edit
                        user_password PATCH  /users/password(.:format)                                                                users/passwords#update
                                      PUT    /users/password(.:format)                                                                users/passwords#update
                                      POST   /users/password(.:format)                                                                users/passwords#create
             cancel_user_registration GET    /users/cancel(.:format)                                                                  users/registrations#cancel
                new_user_registration GET    /users/sign_up(.:format)                                                                 users/registrations#new
               edit_user_registration GET    /users/edit(.:format)                                                                    users/registrations#edit
                    user_registration PATCH  /users(.:format)                                                                         users/registrations#update
                                      PUT    /users(.:format)                                                                         users/registrations#update
                                      DELETE /users(.:format)                                                                         users/registrations#destroy
                                      POST   /users(.:format)                                                                         users/registrations#create
                             families GET    /families(.:format)                                                                      families#index
                               family GET    /families/:id(.:format)                                                                  families#show
                                 root GET    /                                                                                        home#index
```
#### deviseのビューの作成
```rails g devise:views families```
```rails g devise:views users```コマンドでviewを作成

#### コントローラーを作成
次のコマンドでコントローラーを作成
```
bundle exec rails generate devise:controllers users
bundle exec rails generate devise:controllers families
```
#### いったん表示されるか確認

```localhost:3000/users/sign_in```
```localhost:3000/families/sign_in```
でそれぞれのページが表示されるか確認する。

#### viewファイルの編集
今回userでの登録はnicknameとパスワードのみで行いたい。
そのためフォームをいじる。
```ruby:app/views/users/regstrations/new.htm.erb
        <%= form_with model: @user, url: user_registration_path, method: :post, local: true do |f| %>

        <div class="field">
          <%= f.label :nickname, "ユーザー名" %><br />
          <%= f.text_field :nickname, id:"nickname" %> 
        </div>

        <div class="field">
          <%= f.label :password, "パスワード（6文字以上）" %><br />
          <%= f.password_field :password, id:"family_password", autocomplete: "new-password" %>
        </div>

        <div class="field">
          <%= f.label :password_confirmation, "パスワード再入力" %><br />
          <%= f.password_field :password_confirmation, id:"family_password_confirmation", autocomplete: "new-password" %>
        </div>

        <div class="actions">
          <%= f.submit "新規登録", class: :form__btn  %>
        </div>
      <% end %>
```
こんな感じにしておく。

また、familyの登録には、family_nameを加えたいので、viewをいじる

```ruby:app/views/families/regstrations/new.htm.erb
<%= render "families/shared/error_messages", resource: resource %>

        
        <%= form_with model: @family, url: family_registration_path, method: :post, local: true do |f| %>

        <div class="field">
          <%= f.label :email, "保護者メールアドレス" %><br />
          <%= f.email_field :email, id:"family_email", autofocus: true, autocomplete: "email" %>
        </div>

        <div class="field">
          <%= f.label :password, "パスワード（6文字以上）" %><br />
          <%= f.password_field :password, id:"family_password", autocomplete: "new-password" %>
        </div>

        <div class="field">
          <%= f.label :password_confirmation, "パスワード再入力" %><br />
          <%= f.password_field :password_confirmation, id:"family_password_confirmation", autocomplete: "new-password" %>
        </div>

        <div class="field">
          <%= f.label :family_name, "家族名" %><br />
          <%= f.text_field :family_name, id:"family_name" %> 
        </div>
        
        <div class="actions">
          <%= f.submit "新規登録", class: :form__btn  %>
        </div>
      <% end %>
```
スタイルシートとかいろいろいじっていい感じにするのに時間がかかったけどそこは、本件と関係ないのではしょる。

#### modelファイルの編集
まずはfamilyのモデルから

```ruby:models/family.rb（編集後）
class Family < ApplicationRecord

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :authentication_keys => [:email]
         validates :family_name, presence: true

         has_many :users, dependent: :destroy
end

```
```:authentication_keys => [:email]```を追記して、familyモデルではemailでの認証のままにしておく。（書かなくてもいいと思って、書かないでやったらエラーが発生したので、カスタムする場合はそれぞれのモデルに書いた方がよさそうです。）
また、```has_many :users, dependent: :destroy```でuserモデルとアソシエーションを記述してfamilyに対してuserを紐づけておく。

```ruby:models/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,
         :authentication_keys => [:nickname] 

  validates :nickname, presence: true, uniqueness: true 
  belongs_to :family

  def email_required?
    false
  end

  def email_changed?
    false
  end

end


```
``` :authentication_keys => [:nickname]```で認証キーを[:nickname]にオーバーライドします。
nicknameで認証するために一意のバリデーションをかけておきます。
```belongs_to :family```でアソシエーションを行い、familyと紐づけておきます。
```
def email_required?
    false
  end

  def email_changed?
    false
  end
```
emailをキーにしていないため、データの更新や変更にemailを必須とするとエラーの原因になるためバリデーションを無効化させています。

#### コントローラーの編集

family_nameが受け取れるように、before_actionでpramaterの要素を追加してあげる。

```ruby:contorollers/families/registrations_controller.rb

before_action :configure_sign_up_params, only: [:create]

private

   def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, :family_name])
  end
end
```
family次にログインの実装のためにsession_contoroller.rbファイルを編集する。
```ruby:controllers/families/session_contoroller.rb
class Families::SessionsController < Devise::SessionsController

 before_action :configure_sign_in_params, only: [:create]

 protected
  def configure_sign_in_params
    devise_parameter_sanitizer.permit(:sign_in, keys: [:email])
  end
end
```
emailが認証キーになるように記述。デフォルト設定のままなので必要ないと思ったが、なぜかemailがpermitできないエラーが出てしまったので、記述した。記述することでエラーが解消された。

次にuserコントローラーの設定
```ruby:controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
before_action :authenticate_family!, if: :devise_controller?
before_action :configure_sign_up_params, only: [:create]

  def new
    authenticate_family!
    @user = User.new
  end

  # ユーザーを登録するアクション
 def create
    authenticate_family!
    super do |user|
       if family_signed_in?
          user.family = current_family
          user.save! 
       end
    end
  end
  private
  def configure_sign_up_params
 
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :password, :password_confirmation, :family_id])
  end

  def sign_up(resource_name, resource)
    if !current_family
       sign_in(resource_name, resource)
    end
  end
end


```

```ruby
before_action :authenticate_family!, if: :devise_controller?
```
これによりfamilyでログインしていない場合userの新規登録ができないようにしています。
```ruby
before_action :configure_sign_up_params, only: [:create]
private
  def configure_sign_up_params
 devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :password, :password_confirmation, :family_id])
  end

```
これにより、:family_idと:nicknameを受け取ることが可能になり、emailは不要となる。

```ruby
  def new
    authenticate_family!
    @user = User.new
  end

  # ユーザーを登録するアクション
  def create
    authenticate_family!
    super do |user|
       if family_signed_in?
          user.family = current_family
          user.save! 
       end
    end
  end
```
authenticate_family!でfamilyで認証していないとアクションができないようにした。
paramaterをカスタマイズしたのでアクションの記述をオーバーライドした。createアクションは、superで元の処理を使いつつ、family_idをどうしてもparamsに含むことができずにエラーが出てしまったので、追加した。

```ruby:controllers/users/session_contoroller.rb
class Users::SessionsController < Devise::SessionsController

before_action :configure_sign_in_params, if: :devise_controller?
  def create
      super
  end
  protected
   def configure_sign_in_params
     
 
 devise_parameter_sanitizer.permit(:sign_in, keys: [:nickname, :password, :remember_me])
  end
end

```

sessionのparamsに[:nickname]が含まれるようにするためにbefore_actionで記述。

これで無事に実装完了しました。

## 終わりに
まとめて書くとこんだけかと思いますが、これを実装するまでに1日丸々費やしました。
間違いや、解釈違いも多いかと思いますので、指摘していただけたらと思います。これから実装する人の一助になれば幸いです。

参考にした記事など

- [[Rails] Deviseを使ってadminユーザーのみがユーザーを登録/編集できるようにする](https://qiita.com/h-sakano/items/7a572dd9691cc2a7d63c)
- [devise の複数モデル管理を試みる](https://www.ccbaxy.xyz/blog/2020/03/20/ruby33/#can-zhao-sururisutono-food-moderuwozuo-cheng-suru)
- [[Rails]メールアドレス以外でサインインできるようにする[devise]
](https://tech.mof-mof.co.jp/blog/devise-login-without-email/)
- [Using devise for multiple models](https://stackoverflow.com/questions/37145991/using-devise-for-multiple-models)