はじめに
フリマのコピーサイトを作る際に、構造をやや難しくしてしまったために苦労したので記録として残そうと思いました。もっと綺麗にかけるなどご指摘あればお願いします。
実装方法
開発環境
- Ruby 2.5.1
- Rails 5.0.7.2
- devise
前提
- deviseは導入済み
- deviseのデフォルト状態でのログイン機能は実装済み
- 1ページ目 → 2ページ目 → トップページ(ログイン)と遷移する
- テーブルは、
usersprofilessending_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への代入にかなり四苦八苦しましたが、おかげでしっかり仕組みなどを考えることができました。細かな説明はしていませんが、アウトプットも兼ねて記事にしてみたので少しでも参考になれば幸いです。