最初に
ユーザー新規登録の際にdevise認証機能を使用して、ユーザーの情報(ユーザーモデル)と、ユーザーの連絡先の情報(addressモデル)を登録できるよにすることが目的であり、1ページでまとめて(fileds_forを使用しない)ではなくウィザード形式を用いて2ページに分割しての実装が難しかったため、備忘録として残します。
ウィザード形式とは
画面が切り替わりながら操作が進んでいくことで、具体的にはこんな感じ。
https://gyazo.com/c0b7e9d98fb66ad7da724a95a77e4438
ユーザー情報(メールアドレスなど)の登録が完了した際に次のページでユーザー情報に付随する住所などの情報をページを切り替えて表示して登録させている。これをウィザード形式と呼ぶ。らしい。
実装の流れ
ユーザー情報を入力させ、その情報を一旦sessionに保持させる。
次に住所の情報を入力させ、入力が完了したらsessionに保持したユーザー情報と住所の情報を各テーブルに保存させる。
ページが切り替わる際にバリデーションをかけられるため、各ページごとにテーブルを分ける必要があります。
例えばユーザー情報登録画面にaddressモデルの住所を登録する項目(カラム)があった場合、userモデルではpermitされていないため、登録が進まないことになってしまう。1ページに1モデル対応させるようにする。
手順
1.deviseのコントローラーをカスタムできるようにする。
deviseのデフォルトの状態だとコントローラを作成していなくてもdevise/registrationsコントローラーが起動して、view/regstrations/new.html.hamlにルーティングが敷かれている。(ルーティングはdevise_for: users)
これだとカスタムができないため、対応するコントローラーを作成する。
$ rails g devise:controllers users
これでusers/registrations_contoroller.rbが作成される。
https://gyazo.com/a8052ca6b6cc4bd1e19ff4e2465cff1b
2.deviseのルーティングを変更
deviseのルーティングを指定する。
この記述でdeviseのルーティングの指定が完了。
devise/registrationsコントローラー → users/registrationsコントローラー
にルーティングを変更することができた。
3.app/controllers/application_controller.rbの修正
deviseのpermitはapplication_controller.rbで行う。
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday])
end
before_action :configure_permitted_parameters, if: :devise_controller?
この記述でdevise_controllerが動いた際に、後に記述したconfigure_permitted_parametersメソッドが始動する様にしている。
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday])
end
この記述により、Strong Parametersで追加したいカラムを追加。
デフォルトで:email、:password は許可されているので記載しなくてOK。
先ほど作ったコントローラー。
クラス名意外消してOK。
あとはいつも通り、コントローラーに記載をしていく。
def new
@user = User.new
end
空のハッシュを生成して、
def new
@user = User.new
end
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]
@address = @user.addresses.build
render :new_address
end
createアクションで実際にsessionsに一度保持して、address情報を登録するページに飛ぶように記載している。
最初これ何やってるか意味がわからなかったので3つに分けます。
・1ページ目(ユーザー情報)で入力した情報のバリデーションのチェック
・1ページ目で入力した情報をsessionに保持させること
・次ページの住所情報登録で使用するインスタンスを生成、次ページへ遷移すること
@user = User.new(sign_up_params)
unless @user.valid?
flash.now[:alert] = @user.errors.full_messages
render :new and return
end
Userモデルのインスタンスを生成し、ユーザー情報登録画面から送られてきたパラメータをインスタンス変数@userに代入し、引数としてapplication_controller.rbで修正したsign_up_paramsを引数とする。
configure_permitted_parametersメソッドなのにsign_up_paramsで引っ張ってこれている。これは謎です。
もしかしたらdeviseが勝手に動いてくれているのかも。
インスタンス変数@userに対してvalid?メソッドを適用することで送られてきたパラメータが指定されたバリデーションに引っかかっているかどうかをチェック。falseになった場合は、エラーメッセージとともにnewアクションへrenderさせるような記述となっています。
@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]
end以下の2文でユーザー情報登録画面での情報をsessionに保持させています。
ウィザード形式上、次ページの入力が完了した段階で各モデルのデータをDBに反映させたいです。なのでここでsessionに保持させる必要があります。この2行ずば抜けて意味不明だったので細かく書きます。。(笑)
session["devise.regist_data"]に値を代入します。この時、sessionにハッシュの形で情報を保持させるために、attributesメソッドを使用して、@userの属性を全てデータとして整形しています。
ここで注意したいのがパスワードはattributesメソッドでは追加できないということ。
paramsから取得して別で追加してあげます。パスワードを再度sessionに代入する必要があり、session["devise.regist_data"][:user]["password"]に再代入している。らしい。全然わからん。(泣)
def new
@user = User.new
end
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]
@address = @user.addresses.build
render :new_address
end
最後の2行で、次ページでユーザー情報に紐ずく住所情報を入力させるため、addresses.buildで生成したインスタンス@userに紐づくAddressモデルのインスタンスを生成して@addressに代入。
住所情報を登録させるページを表示するためnew_addressアクションのビューへrenderしています。
5.new_addressへのルーティング指定と対応するビューを移動。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations'
}
devise_scope :user do
get 'addresses', to: 'users/registrations#new_address'
post 'addresses', to: 'users/registrations#create_address'
end
end
devise_scope :user do~endでルーティングを指定。これで#new_addressと#create_addressが使用できるようになる。
対応するnew_address.html.hamlファイルは、
app/views/devise/registrations/下に移動。これでusers/registrationsのアクションでビューが表示される。
6.create_addressアクションでユーザー情報とプロフィール情報をテーブルに保存。
def create_address
@user = User.new(session["devise.regist_data"]["user"])
@address = Address.new(address_params)
unless @address.valid?
flash.now[:alert] = @address.errors.full_messages
render :new_address and return
end
@user.addresses.build(@address.attributes)
@user.save
session["devise.regist_data"]["user"].clear
sign_in(:user, @user)
redirect_to root_path
end
private
def address_params
params.require(:address).permit(:post_code, :prefectures, :city, :block, :building, :phone_number)
end
さきほどsessionにuserオブジェクトのデータを保存したので、create_addressアクションでもuserオブジェクトを生成して@userに代入。2行目でprivateメソッド下の引数で address_paramsメソッドを引数としてAddressモデルを生成、@addressに代入。
あとは先ほどと同じで、addressオブジェクトのバリデーションを確認して、通ったら保存。
addressesテーブルに外部キーを設定していると、バリデーションに引っかかるので、モデルでoptional: trueを追加。
class Address < ApplicationRecord
belongs_to :user, optional: true
end
optional: trueは外部キーにnullが入ることを許可してくれるオプション。これでバリデーションに引っ掛からなくなる。
その後はuserモデルに紐付いたaddressモデルという記述を追加。
@userをsaveして、sessionをclearメソッドを使用して削除。
まだ登録できただけなので、sign_inメソッドでサインイン。
あとはcreate_address.html.hamlが動くかを確認して、正常に動けばウィザード形式の実装完了です!
##参考文献
・https://satoryu.hatenablog.com/entry/antipattern_in_designing_models_for_wizard
・https://note.com/vixer93/n/nac92cc4c0983