この記事で学べること
deviseを導入した状態でウィザード形式(画面遷移がある)のユーザ登録する方法
ウィザード形式での遷移時のバリデーションチェックをする方法
概要
ウィザード形式の導入手順(sessionメソッドを使う)
deviseを導入しているときに自分でユーザ新規登録機能を実装するときの手順
新規登録→自分でコードを書く(ウィザード形式)
ログイン/編集/ログアウト→deviseのメソッドを活用
↓
sign_in/current_userなどのdeviseのメソッドを活用できる
自分で記述するコード量をへらすことができる
なぜdeviseのコントローラーをカスタムしないのか
→頑張ればできるかもだけど難しそうだし自分で実装したほうが楽な気がする😆


deviseの設定は終わっている前提で進めます
モデル/テーブル
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates ...
下記省略...
end
deviseコマンドで作成したuserテーブルに必要なカラムを追加してください
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.string "nickname", null: false
t.string "lastname", null: false
t.string "firstname", null: false
t.string "lastname_kana", null: false
t.string "firstname_kana", null: false
t.integer "birthday_year", null: false
t.integer "birthday_month", null: false
t.integer "birthday_day", null: false
t.string "phonenumber", null: false
t.string "image"
t.text "introduction"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["phonenumber"], name: "index_users_on_phonenumber", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
ルーティング
deviseの機能とは別なので新規登録用のコントローラーとルーティングを設定。
画面の遷移回数に応じてstepの数は調整してください
resources :signups, only: [:index] do
collection do
get 'step1' # 登録情報の一部を入力してもらう
post 'step1', to: 'signups#step1_validates' #step1で入力したデータのバリデーションチャックをする(詳しくはコントローラーで説明) render後にリロードしてもエラーが出ないようにget'step1'とURLは同じに設定する
get 'step2' # 残りの登録情報を入力してもらう
post 'step2', to: 'signups#create' # 入力した情報すべてを保存 こっちもrenderするなら揃える
end
省略...
end
collectionについては下記を参考にしてください
railsのroutes.rbのmemberとcollectionの違いをわかりやすく解説してみた。〜rails初心者から中級者へ〜
コントローラー
stepごとの入力データはsessionメソッドで保持する
deviseを導入しているとparamsでpasswordを渡そうとしても隠されてsessionに代入することができなかった(詳しくはどうなっているのかはわからなかったです。詳しい方は教えていただけると助かります!)
↓
password_confirmationだと隠されない
↓
passwordのかわりにpassword_confirmation代入
セキュリティ的に大丈夫なのかは不明なのでいいやり方を知ってる方がいたら教えていただけると助かります(_ _)
保存されるパスワード自体は暗号化されます
詳しい説明はコントローラーファイルにコメントしてます
sessionについての詳しい説明は下記を参考にしてください
class SignupsController < ApplicationController
def step1
@user = User.new
end
def step1_validates #step1で入力された情報のバリデーションチャックをするためのアクション
# バリデーションチャックするためにインスタンスを作成
@user = set_user_when_email(user_params)
#バリデーションチャック
@user.valid? #無効な値の場合はfalseとインスタンスに対してエラーメッセージを追加してくれる
#今回はstep1でphonenumberを入力しないので設定しているpresence: trueに引っかかっている
#このメソッドでphonenumberのエラー内容を削除してバリデーションを通過させる。メソッドはprivateにあります
skip_phonenumber_validate(@user.errors)
# verify_recaptcha~~ はロボット確認の為のメソッドで今回は殆ど関係ないです。詳しくはgem 'recaptcha'で調べてください
if verify_recaptcha(model: @user, message: "選択してください") && @user.errors.messages.blank? && @user.errors.details.blank?
create_session(user_params)
redirect_to step2_signups_path #step1で入力したデータにバリデーションエラーがない場合はstep2に遷移する
else
@user.errors.messages[:birthday_day] = change_birthday_validate_message(@user) #エラーメッセージを変更するメソッドで今回は関係ないので説明は省略します
render :step1
end
end
def step2
#入力フォームのためのインスタンスを作成
@user = User.new
end
def create
set_user_with_session
@user[:phonenumber] = user_params[:phonenumber] ## step2で入力した電話番号を代入
if @user.save
sign_in User.find(@user.id) unless user_signed_in?
delete_session
redirect_to addresses_path
else
render :step2
end
end
private
def user_params
params.require(:user).permit(:nickname, :email, :password, :password_confirmation, :lastname, :firstname,
:lastname_kana, :firstname_kana, :birthday_year,
:birthday_month, :birthday_day, :phonenumber)
end
def set_user_when_email(user_params)
User.new(
nickname: user_params[:nickname],
email: user_params[:email],
password: user_params[:password_confirmation],
password_confirmation: user_params[:password_confirmation],
lastname: user_params[:lastname],
firstname: user_params[:firstname],
lastname_kana: user_params[:lastname_kana],
firstname_kana: user_params[:firstname_kana],
birthday_year: user_params[:birthday_year],
birthday_month: user_params[:birthday_month],
birthday_day: user_params[:birthday_day]
)
end
def set_user_with_session
@user = User.new(
nickname: session[:nickname],
email: session[:email],
password: session[:password_confirmation],
password_confirmation: session[:password_confirmation],
lastname: session[:lastname],
firstname: session[:firstname],
lastname_kana: session[:lastname_kana],
firstname_kana: session[:firstname_kana],
birthday_year: session[:birthday_year],
birthday_month: session[:birthday_month],
birthday_day: session[:birthday_day]
)
end
# 電話番号はstep1で入力しないのでバリデーションエラーがあったときにスキップさせるためのメソッド
# step1で入力しないカラムのバリデーションエラーメッセージを削除する。
def skip_phonenumber_validate(errors)
errors.messages.delete(:phonenumber) #stepの回数や入力するデータに合わせて変更してください
errors.details.delete(:phonenumber)
end
# 生年月日のどれかにひとつでもバリデーションエラーがあった場合は同じエラーメッセージを表示する
def change_birthday_validate_message(user)
if user.errors.messages[:birthday_year].any? || user.errors.messages[:birthday_month].any? || user.errors.messages[:birthday_day].any?
user.errors.messages.delete(:birthday_year)
user.errors.messages.delete(:birthday_month)
user.errors.messages[:birthday_year] = ["生年月日は正しく入力してください"]
user.errors.messages[:birthday_year]
end
end
def create_session(user_params)
session[:nickname] = user_params[:nickname]
session[:email] = user_params[:email]
session[:password] = user_params[:password_confirmation]
session[:password_confirmation] = user_params[:password_confirmation]
session[:lastname] = user_params[:lastname]
session[:firstname] = user_params[:firstname]
session[:lastname_kana] = user_params[:lastname_kana]
session[:firstname_kana] = user_params[:firstname_kana]
session[:birthday_year] = user_params[:birthday_year]
session[:birthday_month] = user_params[:birthday_month]
session[:birthday_day] = user_params[:birthday_day]
end
def delete_session
session.delete(:nickname)
session.delete(:email)
session.delete(:password)
session.delete(:password_confirmation)
session.delete(:lastname)
session.delete(:firstname)
session.delete(:lastname_kana)
session.delete(:firstname_kana)
session.delete(:birthday_year)
session.delete(:birthday_month)
session.delete(:birthday_day)
session.delete(:pid)
session.delete(:provider)
end
end
今回はバリデーションチェックをするためにpostアクションを使ったが、before_acitonでやる方法もある
→before_acitonだとrender後にリロードするとエラーが出たので採用しませんでした。なぜエラーが出るかは下記を参考にしてください
現場から学ぶ、RailsのControllerアンチパターン
アンチパターン2で解説されてます
なにか解決方法があるかもしれないのでbefore_aciton使用するときのリンク貼っておきます
view
普段どおりに入力フォームを作ってください
= form_for @user, url: step1_signups_path, method: :post do |f|
- required = '必須'
.user_information
.field
.field-label
= f.label :"ニックネーム", class: 'left-label'
%span.min #{required}
#renderされたときにエラーの有無でフォームを変えている
- if @user.errors.messages[:nickname].any?
.field-input
= f.text_field :nickname, autofocus: true, class: 'user_nickname_error', id: 'nickname', placeholder: '例)フリマ123'
= render 'shard/signups_errors', errors: @user.errors.full_messages_for(:nickname)
- else
.field-input
= f.text_field :nickname, autofocus: true, class: 'user_nickname', id: 'nickname', placeholder: '例)フリマ123'
下記省略...
今回は2ページで全情報を入力するので残りのデータを入力するためのフォームを作る
= form_for @user, url: step2_signups_path, method: :post do |f|
.user_phonenumber
.field
.number_presentment
= f.label :"携帯電話の番号"
- if @user.errors.messages[:phonenumber].any?
.number_input
= f.text_field :phonenumber, class: "number_text_error", id: 'phonenumber_error', placeholder: '携帯電話の番号を入力'
= render 'shard/signups_errors', errors: @user.errors.full_messages_for(:phonenumber)
- else
.number_input
= f.text_field :phonenumber, class: "number_text", id: 'phonenumber', placeholder: '携帯電話の番号を入力'
.next_btn
= f.submit :"次へ進む", class: "next_submit"
最後に
機能は実装できましたが、正しいやり方なのか自信がないので皆さんからの指摘や助言をお待ちしています。
この記事が少しでも学習の助けになればと思います。
簡単ですがテストやomnuauth認証を利用したユーザ登録も記事にしたのでよかったら参考にしてください
devise+ウィザード形式でのユーザ登録のRspecテスト
devise+omniauth-rails_csrf_protection+[facebook/google]を使ったユーザ登録とログイン
参考にした記事
[Rails]新規登録画面のウィザードフォーム実装(sessionでデータ保持する)
[Rails]gemを使わないWizard形式の登録フォームを作った
[Rails]ウィザードフォーム実装でfields_forを使って複数のテーブルに保存する