[Ruby] [Rails] Implementation of new registration function in wizard format using devise

4 minute read

Introduction

When making a flea market, I struggled because the structure was a little difficult, so I decided to keep it as a record. If you have any suggestions, such as making the call more beautiful, please.

Implementation method

Development environment

  • Ruby 2.5.1
  • Rails 5.0.7.2
  • devise

premise

DB design

users table

|Column|Type|Options| |——|—-|——-| |name|string|null: false, unique: true, index:true| |email|string|null: false, unique: true, index:true| |password|string|null: false|

profiles table

|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 table

|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|

code

Creating ###controller

  • create a users controller under devise management

terminal


$ rails g devise:controllers users
  • Set routing to refer to which controller

routes.rb


Rails.application.routes.draw do
  devise_for :users, controllers: {registrations:'users/registrations'}
  root to:'items#index'
end

Describe the association in the model

  • Association is described in each model of User, Profile, and Sending_destination *Validation is omitted.

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 is an option that allows the foreign key to be null.

Edit the view corresponding to ###new action (1st page)

  • Describe new action in users/registrations_controller.rb

users/registrations_controller.rb


class Users::RegistrationsController <Devise::RegistrationsController
  # Omitted
  def new
    @user = User.new
    @user.build_profile
  end
  # Omitted
end
 
  • Edit registrations/new.html.haml corresponding to new action

ruby: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 "Next"

Actually, there is a description of div, label, and class, but I wrote it simply. We are using fields_for to handle the two models. Refer to the following articles. [Rails] Method to send values to two models at the same time in devise form (Example: User model and Profile model) [Rails] How to register data to multiple models with one form_for

Edit ###create action

  • Validation check of the information entered on the first page
  • Make the session retain the information entered on page 1
  • Generate an instance to be used in the next address information registration and transition to the relevant page The above 3 points will be done by the create action.

users/registrations_controller.rb


class Users::RegistrationsController <Devise::RegistrationsController
  # Omitted
  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
  # Omitted
  protected
  # Omitted
end

In the valid? method we check if the parameter does not violate the validation. We use the function session that holds the information on the client side so that the information will not be lost even if the page transitions. In order to store information in the form of hash object in session, data is formatted using attributes method. Also, although the password information is included in params, the password information is not included when the data is shaped by the attributes method. So you need to assign the password to session again. Create an instance of the sending_destination model associated with the instance @user created this time with build_sending_destination. Then, render to the view of the new_sending_destination action that displays the page to register the address information.

edit view corresponding to new_sending_destination action (2nd page)

  • Setting the routing of the new_sending_destination action that displays the address registration page
  • Routing setting of the create_sending_destination action that registers the address

    routes.rb

    ```ruby

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を作成


#### **`ruby: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`**
```ruby

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への代入にかなり四苦八苦しましたが、おかげでしっかり仕組みなどを考えることができました。細かな説明はしていませんが、アウトプットも兼ねて記事にしてみたので少しでも参考になれば幸いです。