ログイン機能の実装(ウィザード形式にするために)

#1.deviseの導入及びユーザーのデフォルトでのログインが可能になるまで
以下の手順で導入

```GEMFILE.
gem 'devise'
```

```ターミナル.
rails g devise:install
```
任意のコントローラー（＊＊＊）を用意して、ルーティングの設定をします。
この場合は、トップページに当たる部分のルーティングの設定をしています。

```config/routes.rb
Rails.application.routes.draw do
  root to: "＊＊＊#index"
end
```
```ターミナル.
rails g controller ＊＊＊
```
今回はindexアクションにおいて、特にモデルとのやり取りなどは行わないので、コントローラ内の記述はしません。(任意でトップページのビューは作成したものを使用)

次いでdeviseにおけるデフォルトのログイン機能を実装します。

```ターミナル.
rails g devise user
```

今回は、deviseのデフォルトで用意されているemailとpasswordを最初のビュー場で登録させてから、次のページにてuserのprofileを登録するようにしたいと思います。
その為、そのまま下記のコマンドを実行していきます。

```ターミナル.
rails db:migrate
```
※ただし、今回とは異なりuserモデルの方に名前等を追加したい場合には、下記のコマンド実行前にマイグレーションファイルの編集をしてカラムの追加の必要性が発生します。
その場合には下記のようにコントローラー及びモデルのバリデーションの記述を一部追加する必要性がある

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:追加カラム名, :追加カラム名])
  end
end
```

```app/models/user.rb
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :追加カラム名, :追加カラム名 ,presence: true
end
```

上記を任意の状況に応じて実行後、追加したカラムを入力できるように、新規登録画面のビューを編集する必要があります。デフォルトではdeviseのビューファイルは隠れているので、以下のコマンドを実行します。

```ターミナル.
rails g devise:views
```

```app/views/devise/registrations/new.html.erb

<%= 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 :追加カラム名 %><br />
    <%= f.text_field :追加カラム名 %>
  </div>

  <div class="field">
    <%= f.label :追加カラム名 %><br />
    <%= f.number_field :追加カラム名 %>
  </div>

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

<%# 以下省略 %>
```

※追加するカラムがない場合にはrails db:migrateを実行後にここまでの操作を省略して、以下から続きを実行
任意のビューに下記の記述を追加してログインしていない場合には新規登録またはログインを実行させる画面に移行させる記述を追記していきます。

```app/views/＊＊＊/index.html.erb
<% if user_signed_in?%>
  <h2>ログインしています</h2>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <h2>ログインしていません</h2>
  <%= link_to "新規登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>
```

#２.ウィザード形式でのユーザーの新規登録を実行出来るようにする
今回やっていくこととしては、ユーザー登録の画面でユーザー情報を入力させ、それをsessionに保持させておきます。そして次のプロフィール情報を登録する画面で名前や年齢、性別を入力させ、最後のステップでsessionに保持していたユーザー情報と、それに関連するプロフィール情報をテーブルに保存する流れで行なっていきます。
まず、まだ作成していないプロフィールモデルの作成を実行します。

```ターミナル.
rails g model profile
```

次いで、Userモデルとのリレーションのために、外部キーとしてuser_idが入るようにして、コマンドを実行します。

```db/migrate/20XXXXX.rb
class CreateProfiles < ActiveRecord::Migration[5.2]
  def change
    create_table :profiles do |t|
      t.string :name, null: false
      t.integer :age, null: false
      t.string :gender, null: false
      t.references :user
      t.timestamps
    end
  end
end
```

```ターミナル.
rails db:migrate
```
マイグレートを実行後、テーブルやカラム作成されているか確認して、モデルのバリデーションおよびアソシエーションを設定します。
Userモデルに対してoptional: trueを設けています。optional: trueは外部キーがnullであることを許可するオプションです。
同様にUserモデルについてもアソシエーションを設定します。

```app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user, optional: true
  validates :name, :age, :gender ,presence: true
end
```

```app/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
  has_one :profile
end
```
次いでdeviseのコントローラを作成し、編集できる形にします。

```ターミナル.
rails g devise:controllers users
```

現状rake routesを行うとdevise管理化のコントローラーが呼ばれてしまっているということが確認できます。
そこで以下のようにroutes.rbを編集して、どのコントローラを参照するのか明示してあげます。

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  root to: "＊＊＊#index"
end
```

編集後に再度rake routesをすると、参照するコントローラが変更されていることが確認できます。（今回はユーザ新規登録に必要なregistrationsコントローラのみに適用）

そしてここからの流れを再度確認すると
●userモデルのインスタンスの作成
●バリデーションチェックしたユーザー情報をsessionに保持してprofileモデルのインスタンスの生成
●バリデーションチェックしたバリデーションチェックしたプロフィール情報とsessionで保持していたユーザー情報を保存
という流れで実装していく必要があります。

####userモデルのインスタンスの作成

```app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
# 省略
  def new
    @user = User.new
  end
# 省略
end
```

####newアクションに対応するフォーム形成

```app/views/devise/registrations/new.html.erb

<h2>ユーザー情報登録</h2>

<%= form_for(@user, url: user_registration_path) do |f| %>
  <%= render "devise/shared/error_messages", resource: @user %>

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

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

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

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

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

上記で元のところからの変更点は、resourceという文字列部分（このdeviseのログイン機能で実装されるモデルが入るresourceとは、仮引数のようなもので実装する状況に応じたモデル名に変更する必要がありそうです）やボタンをnextにするなど細かい部分を修正しています。
変更後にビューが反映されているかを確認します。
確認後はユーザー登録フォーム上で"Next"をクリックすると、次の情報を登録するページに遷移しますが、その前にcreateアクション内で追記する必要があるので下記のように編集を実施します。

####createアクションの編集

```app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]

# 省略

   def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @profile = @user.build_profile
    render :new_profiles
  end

# 省略

  protected

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  end

end
```

上記の編集によってどのようになったかを項目ごとに確認していきます。

#####1ページ目で入力した情報のバリデーションチェック
まず、Userモデルのインスタンスを生成し、最初の画面から送られてきたパラメータをインスタンス変数@userに代入します。そのインスタンス変数に対してvalid?メソッドを適用することで送られてきたパラメータが指定されたバリデーションに違反しないかどうかチェックしています。falseになった場合は、エラーメッセージとともにnewアクションへrenderさせます。

#####1ページで入力した情報をsessionに保持させること
次いで入力した情報をsessionに保持させます。
今回のようにウィザード形式にする場合には最後のページまで遷移した後に保存するというようにするために、今回はsessionという機能を用います。
バリデーションチェックが完了したら、session["devise.regist_data"]に値を代入します。この時、sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。また、paramsの中にはパスワードの情報は含まれていますが、attributesメソッドでデータ整形をした際にパスワードの情報は含まれていません。そこで、パスワードを再度sessionに代入する必要があります。

#####次画面にてプロフィール情報登録で使用するインスタンスを生成、当該ページへ遷移すること
次画面では、このユーザーモデルに紐づくプロフィール情報を入力させるため、該当するインスタンスを生成しておく必要があります。そのために、build_profileで今回生成したインスタンス@userに紐づくProfileモデルのインスタンスを生成します。ここで生成したProfileモデルのインスタンスは、@profileというインスタンス変数に代入します。そして、プロフィール情を登録させるページを表示するnew_profileアクションのビューへrenderしています。

次にnew_profileアクションとcreate_profileアクションのルーティングを設定します。

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'profiles', to: 'users/registrations#new_profile'
    post 'profiles', to: 'users/registrations#create_profile'
  end
  root to: "＊＊＊#index"
end
```

```app/views/devise/registrations/new_profile.html.erb
<h2>プロフィール情報登録</h2>

<%= form_for @profile do |f| %>
  <%= render "devise/shared/error_messages", resource: @profile %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <div class="field">
    <%= f.label :age %><br />
    <%= f.text_field :age %>
  </div>

  <div class="field">
    <%= f.label :gender %><br />
    <%= f.text_field :gender %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```
ルーティング先のビューも下記のように作成して、画面遷移の確認をします。

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'profiles', to: 'users/registrations#new_profile'
    post 'profiles', to: 'users/registrations#create_profile'
  end
  root to: "＊＊＊#index"
end
```

最後にcreate_profileアクションで、ユーザー情報とプロフィール情報全てをテーブルに保存するように実装します。
#####create_profileアクションの再編集

```app/controllers/users/registrations_controller.rb
#省略

  def create_profile
    @user = User.new(session["devise.regist_data"]["user"])
    @profile = Profile.new(profile_params)
    unless @profile.valid?
      flash.now[:alert] = @profile.errors.full_messages
      render :new_profile and return
    end
    @user.build_profile(@profile.attributes)
    @user.save
    sign_in(:user, @user)
  end

#省略

  protected

  def profile_params
    params.require(:profile).permit(:name, :age, :gender)
  end
```

上記の編集によってどのようになったかを項目ごとに確認していきます。

#####2ページ目で入力したプロフィール情報のバリデーションチェック
最初の入力画面でのcreateアクションと同様に、valid?メソッドを用いて、バリデーションチェックを行います。

#####バリデーションチェックが完了した情報と、sessionで保持していた情報とあわせ、ユーザー情報として保存すること
build_profileを用いて送られてきたparamsを、保持していたsessionが含まれる@userに代入します。そしてsaveメソッドを用いてテーブルに保存します。

#####ログインをすること
ユーザーの新規登録ができても、ログインができているわけではありません。それをsign_inメソッドを利用してログイン作業を行いましょう。
上記が完了したらprofile_createに対応するビューを作成します。

```app/views/devise/registrations/create_profile.html.erb
<h2>登録が完了しました</h2>
<%= link_to "トップへ戻る", root_path%>
```

#####sessionを削除すること

このようにすることでユーザー登録画面をウィザード形式にすることが可能となります。
次の記事ではSNS認証をこちらに追加していきたいと思います。