LoginSignup
1
1

More than 3 years have passed since last update.

[Rails]deviseを使ったウィザード形式での新規登録機能の実装

Posted at

はじめに

フリマのコピーサイトを作る際に、構造をやや難しくしてしまったために苦労したので記録として残そうと思いました。もっと綺麗にかけるなどご指摘あればお願いします。

実装方法

開発環境

  • Ruby 2.5.1
  • Rails 5.0.7.2
  • devise

前提

DB設計

usersテーブル

Column Type Options
name string null: false, unique: true, index:true
email 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
  • どのコントローラーを参照するのかルーティングを設定
routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: { registrations: 'users/registrations' }
  root to: 'items#index'
end

モデルにアソシエーションを記述する

  • UserProfileSending_destinationそれぞれのモデルにアソシエーションを記述
    ※バリデーションは省略しています。
user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_one :profile
  accepts_nested_attributes_for :profile
  has_one :sending_destination
end
profile.rb
class Profile < ApplicationRecord
  belongs_to :user, optional: true
end
sending_destination.rb
class SendingDestination < ApplicationRecord
  belongs_to :user, optional: true
end

optional: trueは外部キーがnullであることを許可するオプションです。

newアクションと対応するビューを編集する(1ページ目)

  • users/registrations_controller.rbにnewアクションを記述
users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  # 省略
  def new
    @user = User.new
    @user.build_profile
  end 
  # 省略
end

  • newアクションに対応するregistrations/new.html.hamlを編集
devise/registrations/new.haml.html
= 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アクションでやることになります。
users/registrations_controller.rb
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アクションのルーティングの設定
routes.rb
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を作成
devise/registrations/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を削除する
  • ログインをする
users/registrations_controller.rb
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への代入にかなり四苦八苦しましたが、おかげでしっかり仕組みなどを考えることができました。細かな説明はしていませんが、アウトプットも兼ねて記事にしてみたので少しでも参考になれば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1