未経験からのフルスタックエンジニア

スキルをつけよう!未経験からフリーランスエンジニアへの成長記録

【Rails】ウィザード形式フォームで新規登録〜Devise+session〜

そもそもウィザード形式フォームとは

❌縦に長いフォーム画面
⭕️複数画面に順に遷移していくフォーム画面

(具体例) Image from Gyazo Image from Gyazo

実装方法

開発環境

Ruby 2.5.1
Rails 5.2.3

結論

以下の記事を参考にして実装しました https://qiita.com/NT90957869/items/56ca4101d7ba37778076 https://qiita.com/NT90957869/items/614842934feff9812203

上記の記事との違いは、Deviseとsessionを組合わせて実装(バリデーションはRailsのモデルに記載)したことです。

記事では、Deviseが使えないとなっていますが、コントローラをDeviseから完全分離させて実装し、ログイン・ログアウト・カレントユーザーのデータ取得などはDeviseを利用するといういいとこ取りをしています。

sessionを用いることで、全ての情報をユーザーが入力した後に、一括でDBに保存することができます。
逆にいうと、途中でユーザーがフォームの入力を辞めた時には、入力データがDBに保存されないので、中途半端なデータが残らない仕様にできます。

補足

他にも以下のような実装方法もあるようです

  • gem wickedを利用する(ただし、画面遷移ごとにDBにデータが保存される模様)
  • JSで画面を切り替える(つまり、画面遷移ではなく、見た目を変える)

コード

下記の記事がわかりやすいです。 https://qiita.com/NT90957869/items/614842934feff9812203

今回は、全体の流れと、上記の記事と違う部分をピックアップして説明していきます。

  1. Deviseの導入
  2. Deviseとは関係のないコントローラの作成
  3. ルーティングの設定
  4. フォームのマークアップ
  5. sessionを使ったデータの保持
  6. 最後にsessionのデータを使って、一括登録
  7. 一括登録後、自動的にサインインさせる

Deviseの導入

Gemfileに下記を記載

gem 'devise'

以下をコンソールで実行

$ bundle install #gemのインストール
$ rails g devise:install #deviseを使うのに必要なファイルの生成
$ rails g devise user #deviseでUserモデルの生成

マイグレーションファイルが生成されるので、Usersテーブルに必要なカラムを指定しマイグレートを実行。
※ もともと記載されているdevise関連の記述は変更しなくて大丈夫です。
passwordカラム、password_confirmationカラムは作成しないでください。左記の記述はビューのフォームで使用しますが、実際のパスワードはdeviseで暗号化され、encrypted_passwordカラムに自動的に保存されます。
(具体例)

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname, null: false
      t.string :email, null: false, unique: true
      t.string :last_name, null: false
      t.string :first_name, null: false
      t.string :last_name_kana, null: false
      t.string :first_name_kana, null: false
      t.integer :birthdate_year, null: false
      t.integer :birthdate_month, null: false
      t.integer :birthdate_day, null: false
      t.integer :phone_number, null: false, unique: true
      t.string :address_last_name, null: false
      t.string :address_first_name, null: false
      t.string :address_last_name_kana, null: false
      t.string :address_first_name_kana, null: false
      t.string :address_number, null: false
      t.integer :address_prefecture, null: false, default: 0
      t.string :address_name, null: false
      t.string :address_block, null: false
      t.string :address_building
      t.integer :address_phone_number
      t.text :introduce
      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
$ bundle exec rake db:migrate

Deviseとは関係のないコントローラの作成

今回は、signup_controller.rbを作成しました。

$ bundle exec rails g controller signup

ルーティングの設定

必要なビューの数(遷移させたい数)だけ、ルーティングを設定してください。
(具体例)

resources :signup do
    collection do
      get 'step1'
      get 'step2'
      get 'step3'
      get 'step4' # ここで、入力の全てが終了する
      get 'done' # 登録完了後のページ
    end
  end

フォームのマークアップ

遷移ページ分だけビューを用意してください。
e.g.) step1.html.haml, step2.html.haml, step3.html.haml, ...etc
各ページのフォームタグのurlで、上記で設定したルーティング(次のページへのパス)を記載します。
(具体例)

= form_for @user, url: step2_signup_path, method: :get, html: {class: 'first-main__box'} do |f|
  = f.text_field :nickname, placeholder: '例) メルカリ太郎'
  = f.email_field :email, placeholder: 'PC・携帯どちらでも可'
  = f.password_field :password, placeholder: '6文字以上'
  = f.password_field :password_confirmation, placeholder: '6文字以上'
= form_for @user, url: step3_signup_path, method: :get, html: {class: 'second-wrapper__box'} do |f|
  = f.text_field :last_name, placeholder: '例) 山田'
  = f.text_field :first_name, placeholder: '例) 彩'
  = f.text_field :last_name_kana, placeholder: '例) ヤマダ'
  = f.text_field :first_name_kana, placeholder: '例) アヤ'

sessionを使ったデータの保持

Railsではsessionという情報保管方法があります
Railsドキュメント http://railsdoc.com/references/session

概略としては、キーバリュー形式で、任意のデータを保管できる仕組みです。

# 保管方法
session[キー] = 値
# 具体例
session[:nickname] = "hoge" # キー":nickname"にバリュー"hoge"を保管
session[:nickname]
-> "hoge"

sessionを利用して、各ページのデータを遷移ごとに保管していきます。 やるべきことは、以下の2つです。

  1. ストロングパラメータの使用
  2. 各アクションでsessionにparamsデータを保管
private
 # 許可するキーを設定します
  def user_params
    params.require(:user).permit(
      :nickname, 
      :email, 
      :password, 
      :password_confirmation, 
      :last_name, 
      :first_name, 
      :last_name_kana, 
      :first_name_kana, 
      ~省略~
  )
  end
class SignupController < ApplicationController
  # 各アクションごとに新規インスタンスを作成します
 # 各アクションごとに、遷移元のページのデータをsessionに保管していきます
  def step1
    @user = User.new # 新規インスタンス作成
  end

  def step2
    # step1で入力された値をsessionに保存
    session[:nickname] = user_params[:nickname]
    session[:email] = user_params[:email]
    session[:password] = user_params[:password]
    session[:password_confirmation] = user_params[:password_confirmation]
    @user = User.new # 新規インスタンス作成
  end

  def step3
    # step2で入力された値をsessionに保存
    session[:last_name] = user_params[:last_name]
    session[:first_name] = user_params[:first_name]
    session[:last_name_kana] = user_params[:last_name_kana]
    session[:first_name_kana] = user_params[:first_name_kana]
    @user = User.new # 新規インスタンス作成
  end

~遷移ページ分繰り返す~

最後にsessionのデータを使って、一括登録

データの入力が全て終了するページで、フォームのurlをcreateアクションが実行されるように記載します。
(具体例)

= form_for @user, url: signup_index_path, method: :post, html: {class: 'forth-main-wrapper__box'} do |f|

createアクション内で、今まで保管したsessionのデータを渡し、DBに保存します。

  def create
    @user = User.new(
      nickname: session[:nickname], # sessionに保存された値をインスタンスに渡す
      email: session[:email],
      password: session[:password],
      password_confirmation: session[:password_confirmation],
      last_name: session[:last_name], 
      first_name: session[:first_name], 
      last_name_kana: session[:last_name_kana], 
      first_name_kana: session[:first_name_kana], 
      ~省略~
    )
    if @user.save
   # ログインするための情報を保管
      session[:id] = @user.id
      redirect_to done_signup_index_path
    else
      render '/signup/registration'
    end
  end

一括登録後、自動的にサインインさせる

deviseのメソッドsign_inを活用し、createアクションで作成・保存したデータのidを用いてサインインさせます。

    def done
      sign_in User.find(session[:id]) unless user_signed_in?
    end

まとめ

以上の方法を使えば、問題なく登録できるかと思います。
deviseを導入しているので、サインインやログアウト、ログインユーザーのデータ取得、サインイン中かどうかの判断など、全てdeviseのメソッドを活用できる点がいいところです。

他テーブルも含めた一括登録もできます。以下の記事を参考にしてみてください。
https://qiita.com/NT90957869/items/614842934feff9812203

注意点

上記の実装のままでは、ページ遷移ごとにバリデーションをチェックできません。 ページ遷移前のパリデーションのチェック方法はまた別記事にまとめたいと思います。