【この記事の説明】
現在、私はプログラミングスクールでプログラミング(主にRuby)の勉強をしています。
そして、その一環でチーム開発(メルカリもどきのサイト作成)を行っており、私はその中で記事のタイトルにあるように、ウィザード形式でユーザの登録ができるようにする作業を担当致しました。
又、スクールからの指示で、この登録フォームにはdeviseを使用するようにとのことでした。
この記事は、その担当作業についてまとめたものになります。
【作業】
ここから私が行った作業(そこで困ったこと)を記事として記載していこうと思います
※この記事では、controllerについて記載したいと思います
controller
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
super
end
def create
@user = User.new(session["devise.regist_data"]["user"])
@profileaddress = Profileaddress.new(session["profileaddress"])
@deliveryaddress = Deliveryaddress.new(deliveryaddresses_params)
unless @deliveryaddress.valid?
flash.now[:alert] = @deliveryaddress.errors.full_messages
render :new_deliveryaddresses and return
end
@user.build_profileaddress(@profileaddress.attributes)
@user.build_deliveryaddress(@deliveryaddress.attributes)
if @user.save
session["devise.regist_data"]["user"].clear
session["profileaddress"].clear
sign_in(:user,@user)
redirect_to root_path
else
render :new
end
end
def new_profileaddresses
@user = User.new(birthday_save)
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]
@profileaddress = @user.build_profileaddress
end
def new_deliveryaddresses
user = User.new(session["devise.regist_data"]["user"])
@profileaddress = Profileaddress.new(profileaddress_params)
unless @profileaddress.valid?
flash.now[:alert] = @profileaddress.errors.full_messages
render :new_profileaddresses and return
end
user.build_profileaddress(@profileaddress.attributes)
session["profileaddress"] = @profileaddress.attributes
@deliveryaddress = user.build_deliveryaddress
end
private
def birthday_save
date = user_params
year = date[:"birthday(1i)"]
month = date[:"birthday(2i)"]
day = date[:"birthday(3i)"]
if year.empty? || month.empty? || day.empty?
return
end
# 年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
birthday = Date.new(year.to_i,month.to_i,day.to_i)
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
end
def user_params
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
# paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
end
def profileaddress_params
params.require(:profileaddress).permit(:postal_code, :prefectures,:city,:address,:building)
end
def deliveryaddresses_params
params.require(:deliveryaddress).permit(:family_name,:given_name,:family_name_kana,:given_name_kana,:postal_code,:prefectures,:city,:address,:building,:phone_number)
end
end
ここで、ウィザード形式を行う上で必要なsessionについて説明します
session
普通、ページ遷移するたびにフォームの内容はリセットされてしまいます
sessionは、一時的にデータを保存し、最終的に保存できるようにするメソッドです
このsessionを使用することで、ウィザード形式は3ページ目でデータを保存することができます
簡単にいうと、
1ページ目データ入力
=>sessionを使って一時保存
=>2ページ目データ入力
=>sessionを使って一時保存
=>3ページ目データ入力
=>sessionでデータを呼び出して全てのデータを保存(ユーザ登録)
このような形になります
sessionとattributes
今回はsessionにハッシュオブジェクトの形で情報を保持させなくてはいけないので、attributesメソッドを用いてデータを整形しています
ただし、パスワードの情報はその中に含まれないので、再度、パスワードだけsessionに代入する必要があります
⦅アソシエーション⦆
次にcontrollerといきたいのですが、その前にモデルでアソシエーションを組まないとcontrollerでコード記述ができないので、モデルの記述を行います
(バリデーションに関しては別記事に記載しますので、ここでは省きます)
class User < ApplicationRecord
has_one :profileaddress, dependent: :destroy
has_one :deliveryaddress, dependent: :destroy
end
class Profileaddress < ApplicationRecord
belongs_to :user, optional: true
# optional: true は外部キーがnullであることを許可するオプション
end
class Deliveryaddress < ApplicationRecord
belongs_to :user,optional: true
end
has_oneとは
belongs_to => 他方のモデルとの間に「1対1」を示したもの
has_one => 他方のモデルとの間に1対1の関連付け示したものだが、belongs_toとの違いは、他方のモデルのインスタンスをまるごと含む形になるというところである
今回で言えば、
Userモデル => has_one
Profileaddressモデル => belongs_to
Deliveryaddressモデル => belongs_to
という形であり、UserモデルのインスタンスがProfileaddressモデルとDeliveryaddressモデルのインスタンスをまるごと含む形です
私の感覚的に言えば、
Userが親だからhas_one
他二つのインスタンスは子だからbelongs_to
という感覚になります
ちなみに、has_oneを使用する時は、belongs_toとセットだという認識で良いそうです
build_association
build_associationメソッドは、has_one,belongs_toをモデルに記述された時に使用できるメソッドです
このメソッドは関連付けられた型の新しいオブジェクトを返します
又、このオブジェクトは渡された属性でインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます
今回を例に取って見ます
@user.build_profileaddress(@profileaddress.attributes)
# @profileaddressにはProfileaddressモデルのインスタンス(データ入り)が代入されている
# @user = User.new(session["devise.regist_data"]["user"])が前提となっている
この場合、Userインスタンスに関連づけられたProfileaddressモデルのインスタンスが生成されているということになります
⦅アクション⦆
では、アクションの中身を記述していきたいと思います
まずはnew_deliveryaddressesアクションです
def new_profileaddresses
@user = User.new(birthday_save)
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]
@profileaddress = @user.build_profileaddress
end
private
def birthday_save
date = user_params
year = date[:"birthday(1i)"]
month = date[:"birthday(2i)"]
day = date[:"birthday(3i)"]
if year.empty? || month.empty? || day.empty?
return
end
# 年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
birthday = Date.new(year.to_i,month.to_i,day.to_i)
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
end
def user_params
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
# paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
end
上記にsessionは一時保存用のメソッドであることは記載いたしました。
そして、コードを見てもらえればわかりますが、session[]にはUserモデルのインスタンスがそのまま入る形となっています。
つまり、sessinonメソッドに入る前にはUserモデルのインスタンスの形として成り立っていないといけません
(sessionにこの形で入らなければいけない制約はありませんが、最終的保存を考えるとここでこの形で一時保存したかったという意味です)
ここで、私が大変だっところは生年月日のデータ保存でした
生年月日のデータ保存
viewの入力フォームは、下記のようになっています
.form-group
生年月日
%span.required
必須
%br
.field
!= sprintf(f.date_select(:birthday, prefix:'birthday',with_css_classes:'XXXXX', prompt:"--",use_month_numbers:true, start_year:Time.now.year, end_year:1900, date_separator:'%s'),'年','月')+'日'
困ったのは、このフォームの形をとると取得するデータの形を
birthda=>{birthday(1i)=>"0000",birthday(2i)=>"0",birthday(3i)=>"0"}
の形で取ってしまうことです
Userテーブルのbirthdayカラムは1つなので、普通に下記のようなストロングパラメータでは弾かれてしまうのです
def user_params
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday)
end
ですので、下記のようにパラメータを作成しました
def user_params
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge("birthday(1i)": params[:birthday][:"birthday(1i)"]).merge("birthday(2i)": params[:birthday][:"birthday(2i)"]).merge("birthday(3i)": params[:birthday][:"birthday(3i)"])
# paramsの中のカラム→:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana,:birthday(1i),:birthday(2i),:birthday(3i)
end
そして、これでは、User.newでUserインスタンスを生成する際もデータが入らないのです
ですので、入るようにデータの形を変更するメソッドを作成しないといけませんでした
それが下記のメソッドになります
def birthday_save
date = user_params
#user_paramsはストロングパラメータのことです
year = date[:"birthday(1i)"]
month = date[:"birthday(2i)"]
day = date[:"birthday(3i)"]
if year.empty? || month.empty? || day.empty?
return
end
#↑empty?は配列の中身が空か、文字列の中身が空の場合にtrueを返す。つまり、空だったら、やり直し
#↓年月日別々できたものを結合して新しいDate型変数を作り,paramsに数字化したDataをくっつけました
birthday = Date.new(year.to_i,month.to_i,day.to_i)
params.require(:user).permit(:email,:password,:password_confirmation,:nickname,:family_name,:given_name,:family_name_kana,:given_name_kana).merge(birthday: birthday)
end
def new_profileaddresses
@user = User.new(birthday_save)
#↑ここでparamsを呼び出している
このメソッドで作成したparamsをnew_profileaddressesアクションで呼び出すことによってUserモデルのインスタンスにデータを代入することができるようになりました
【次の記事】
次の記事はバリデーションについて記載したいと思います
次の記事 → ウィザード形式でユーザ登録のフォームを作成してみた③-1 〜バリデーション〜
【参考】
ウィザードフォーム形式フォームの実装 -devise×session×データを複数テーブルに分けて保存 ver.- (rails)
Rails の session を完全に理解した
[Rails] delegateってなに。 solidus
Active Record の関連付け
【Rails】date_selectタグの使い方メモ
nil? empty? blank? present? の使い分け