はじめに
フリマのコピーサイトを作る際に、構造をやや難しくしてしまったために苦労したので記録として残そうと思いました。もっと綺麗にかけるなどご指摘あればお願いします。
実装方法
開発環境
- Ruby 2.5.1
- Rails 5.0.7.2
- devise
###前提
- deviseは導入済み
- deviseのデフォルト状態でのログイン機能は実装済み
- 1ページ目 → 2ページ目 → トップページ(ログイン)と遷移する
- テーブルは、
users
profiles
sending_destinations
の3つ - 1ページ目は1つのフォームで
Userモデル
とProfileモデル
の二つを扱っている
※2つのモデルに同時に値を送る方法はこちらの記事を参考にしています。
【Rails】deviseのフォームで2つのモデルに同時に値を送る方法(例: UserモデルとProfileモデル)
DB設計
usersテーブル
Column | Type | Options |
---|---|---|
name | string | null: false, unique: true, index:true |
string | null: false, unique: true, index:true | |
password | string | null: false |
profilesテーブル
Column | Type | Options |
---|---|---|
first_name | string | null: false |
family_name | string | null: false |
first_name_kana | string | null: false |
family_name_kana | string | null: false |
introduction | string | null: true |
year | integer | null: false |
month | integer | null: false |
day | integer | null: false |
sending_destinations(住所)テーブル
Column | Type | Options |
---|---|---|
first_name | string | null :false |
family_name | string | null: false |
first_name_kana | string | null: false |
family_name_kana | string | null: false |
post_code | string | null: false |
prefecture | string | null: false |
city | string | null:false |
house_number | string | null: false |
building_name | string | - |
phone_number | string | unique: true, null: true |
user_id | references | null: false, foreign_key: true |
##コード
###controllerの作成
- devise管理下の
users
コントローラーを作成する
$ rails g devise:controllers users
- どのコントローラーを参照するのかルーティングを設定
Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'users/registrations' }
root to: 'items#index'
end
###モデルにアソシエーションを記述する
-
User
、Profile
、Sending_destination
それぞれのモデルにアソシエーションを記述
※バリデーションは省略しています。
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :profile
accepts_nested_attributes_for :profile
has_one :sending_destination
end
class Profile < ApplicationRecord
belongs_to :user, optional: true
end
class SendingDestination < ApplicationRecord
belongs_to :user, optional: true
end
optional: true
は外部キーがnullであることを許可するオプションです。
###newアクションと対応するビューを編集する(1ページ目)
- users/registrations_controller.rbにnewアクションを記述
class Users::RegistrationsController < Devise::RegistrationsController
# 省略
def new
@user = User.new
@user.build_profile
end
# 省略
end
- newアクションに対応するregistrations/new.html.hamlを編集
= form_for(@user, url: user_registration_path) do |f|
= render "devise/shared/error_messages", resource: @user
= f.text_field :nickname
= f.email_field :email
= f.password_field :password
= f.password_field :password_confirmation
= f.fields_for :profile do |p|
= p.text_field :family_name
= p.text_field :first_name
= p.text_field :family_name_kana
= p.text_field :first_name_kana
= p.select :year
= p.select :month
= p.select :day
= f.submit "次へ"
実際はdivやlabel、classの記載がありますが、簡素に書いています。
2つのモデルを扱うためにfields_for
を利用しています。以下の記事を参考にしています。
【Rails】deviseのフォームで2つのモデルに同時に値を送る方法(例: UserモデルとProfileモデル)
【Rails】1つのform_forで複数モデルへデータ登録をする方法
###createアクションを編集
- 1ページ目で入力した情報のバリデーションチェック
- 1ページで入力した情報をsessionに保持させる
- 次の住所情報登録で使用するインスタンスを生成、当該ページへ遷移する
以上の3点がcreateアクションでやることになります。
class Users::RegistrationsController < Devise::RegistrationsController
# 省略
def create
@user = User.new(sign_up_params)
@user.build_profile(sign_up_params[:profile_attributes])
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]
session[:profile_attributes] = sign_up_params[:profile_attributes]
@sending_destination = @user.build_sending_destination
render :new_sending_destination
end
# 省略
protected
# 省略
end
valid?
メソッドでパラメータがバリデーションに違反しないかどうかチェックします。
ページが遷移しても情報が消えることが無いように、クライアント側で保持をさせておく機能session
を用いています。
sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。また、paramsの中にはパスワードの情報は含まれていますが、attributesメソッドでデータ整形をした際にパスワードの情報は含まれていません。そこで、パスワードを再度sessionに代入する必要があります。
build_sending_destination
で今回生成したインスタンス@user
に紐づくsending_destination
モデルのインスタンスを生成します。
そして、住所情報を登録させるページを表示するnew_sending_destination
アクションのビューへrenderします。
new_sending_destinationアクションと対応するビューを編集する(2ページ目)
- 住所登録をするページを表示する
new_sending_destination
アクションのルーティングの設定 - 住所を登録する
create_sending_destination
アクションのルーティングの設定
Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'users/registrations' }
devise_scope :user do
get 'sending_destinations', to: 'users/registrations#new_sending_destination'
post 'sending_destinations', to: 'users/registrations#create_sending_destination'
end
root to: 'items#index'
end
- 該当するビューファイルであるnew_sending_destination.html.hamlを作成
= form_for @sending_destination do |f|
= render "devise/shared/error_messages", resource: @sending_destination
= f.text_field :family_name
= f.text_field :first_name
= f.text_field :family_name_kana
= f.text_field :first_name_kana
= f.text_field :post_code
= f.text_field :prefecture
= f.text_field :city
= f.text_field :house_number
= f.text_field :building_name
= f.number_field :phone_number
= f.submit "登録する"
create_sending_destinationアクションを編集する
- 2ページ目で入力した住所情報のバリデーションチェック
- バリデーションチェックが完了した情報と、sessionで保持していた情報とあわせ、ユーザー情報として保存する
- sessionを削除する
- ログインをする
class Users::RegistrationsController < Devise::RegistrationsController
# 省略
def create_sending_destination
@user = User.new(session["devise.regist_data"]["user"])
@profile = @user.build_profile(session[:profile_attributes])
@sending_destination = SendingDestination.new(sending_destination_params)
unless @sending_destination.valid?
flash.now[:alert] = @sending_destination.errors.full_messages
render :new_sending_destination and return
end
@user.build_sending_destination(@sending_destination.attributes)
@user.save
@profile.save
session["devise.regist_data"]["user"].clear
session[:profile_attributes].clear
sign_in(:user, @user)
redirect_to root_path
end
protected
def sending_destination_params
params.require(:sending_destination).permit(
:first_name,
:family_name,
:first_name_kana,
:family_name_kana,
:post_code,
:prefecture, :city,
:house_number,
:building_name,
:phone_number
)
end
end
@user
と@prfile
それぞれのインスタンスにsessionで保持した情報を代入しています。
2ページ目の住所情報をvalid?
でチェックします。
build_sending_destinationを用いて送られてきたparamsを、保持していたsessionが含まれる@user
に代入します。そしてsave
メソッドを用いてテーブルに保存します。
clear
メソッドを用いてsessionを削除します。
sign_in(:user, @user)
でログインし、redirect_to root_path
でトップページに遷移します。
おわりに
以上の方法で実装できます。1ページ目でfields_for
を用いたことでsessionへの代入にかなり四苦八苦しましたが、おかげでしっかり仕組みなどを考えることができました。細かな説明はしていませんが、アウトプットも兼ねて記事にしてみたので少しでも参考になれば幸いです。