記事概要
Ruby on Railsにウィザード形式のユーザー管理機能を実装する方法について、まとめる
前提
- Ruby on Railsでアプリケーションを作成している
- Gemのdeviseを導入している
サンプルアプリ(GitHub)
ウィザード形式
対話するように順番に操作が進んでいく方式のこと
1ページで全ての情報を登録するのではなく、いくつかに分類した情報ごとにページを切り替えて登録を行う
特徴:どの情報を、どのページで登録しているのか分かるので、ユーザーが使いやすい
DB構造
画面遷移図
※ページが切り替わるときに都度バリデーションのチェックを行うため、ウィザードの各ページごとにテーブルを分ける必要がある
手順1(ログイン機能の実装)
Userモデルを作成
- Userモデルを作成する
% rails g devise user
- nameカラムとageカラムを追加するため、マイグレーションファイルを編集する
db/migrate/20************_devise_create_users.rb
# frozen_string_literal: true class DeviseCreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name, null: false # nameカラム t.integer :age, null: false # ageカラム ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" # 省略
- マイグレートを実行する
% rails db:migrate
- Sequel Proを開いてテーブルが作成されているか確認し、nameカラムとageカラムが追加されていることを確認する
- nameカラムとageカラムを追加したので、Application Controllerを編集する
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :age]) end end
- nameカラムとageカラムにバリデーションを設定するため、Userモデルを編集する
app/models/user.rb
class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable validates :name, :age ,presence: true # nameカラムとageカラムにバリデーションを設定 end
ビューを編集
- deviseのビューファイルを表示する
% rails g devise:views
-
app/views/devise/registrations/new.html.erb
を編集し、新規登録画面にname項目とage項目を追加する<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <%#= name項目 %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <%#= age項目 %> <div class="field"> <%= f.label :age %><br /> <%= f.text_field :age %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <%#= 省略 %>
-
app/views/home/index.html.erb
を編集し、ログイン画面と新規登録画面へのリンクを設定する<h1>トップページ</h1> <%#= ログイン画面と新規登録画面へのリンクを設定 %> <% if user_signed_in?%> <h2>ログインしています</h2> <%= link_to "ログアウト", destroy_user_session_path, data: { turbo_method: :delete } %> <% else %> <h2>ログインしていません</h2> <%= link_to "新規登録", new_user_registration_path %> <%= link_to "ログイン", new_user_session_path %> <% end %>
挙動を確認
- サーバーを起動する
- 新規登録し、ログイン状態に遷移できるかをブラウザで確認する
手順2(ウィザード形式の新規登録)
Addressモデルを作成
- Addressモデルを作成する
% rails g model address
- postal_codeカラムとaddressカラムを追加するため、マイグレーションファイルを編集する
db/migrate/20************_devise_create_addresses.rb
class CreateAddresses < ActiveRecord::Migration[7.1] def change create_table :addresses do |t| t.integer :postal_code, null: false # postal_codeカラム t.text :address, null: false # addressカラム t.references :user, foreign_key: true, null: false # Userモデルと紐付け t.timestamps end end end
- マイグレートを実行する
% rails db:migrate
- Sequel Proを開いてテーブルが作成されているか確認し、postal_codeカラムとaddressカラム、user_idカラムが追加されていることを確認する
- バリデーションなどを設定するため、Addressモデルを編集する
app/models/address.rb
class Address < ApplicationRecord belongs_to :user, optional: true # belongs_toの外部キーがnilであることを許可するオプション validates :postal_code, :address ,presence: true # postal_codeカラムとaddressカラムにバリデーションを設定 end
-
Userモデルに対して、
optional: true
を設定する理由
addressesテーブルのuser_id
が外部キーになる
住所情報を入力した時点で、user_id
には値が入っていない(sessionに保持しているが、DB未登録)ので、通常ではバリデーションに引っかかってしまうoptional: true
を設定することにより、外部キーがnil
であってもバリデーションを通過することができるDBに保存する段階では
user_id
が必ず存在してほしいので、マイグレーションファイルでは外部キーにnull: false
を設定する
-
Userモデルにアソシエーションを設定
- Userモデルに、アソシエーションを設定する
app/models/user.rb
# 省略 validates :name, :age ,presence: true # nameカラムとageカラムにバリデーションを設定 has_one :address # アソシエーション(一人のユーザー情報の中に住所の情報が紐づく) end
ルーティングを設定
-
deviseのコントローラーを作成する
% rails g devise:controllers users
-
rails routes
でルーティングを確認すると、devise管理下のコントローラーが紐付き先として表示されるPrefix Verb URI Pattern Controller#Action cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel new_user_registration GET /users/sign_up(.:format) devise/registrations#new edit_user_registration GET /users/edit(.:format) devise/registrations#edit user_registration PATCH /users(.:format) devise/registrations#update PUT /users(.:format) devise/registrations#update DELETE /users(.:format) devise/registrations#destroy POST /users(.:format) devise/registrations#create
-
どのコントローラーと紐づけるのかを明示するため、routes.rbを編集する
config/routes.rbRails.application.routes.draw do #devise_for :users #get 'home/index' # ユーザー新規登録に必要なregistrationsコントローラーのみに適用 devise_for :users, controllers: { registrations: 'users/registrations' } root to: "home#index" end
-
rails routes
でルーティングを確認すると、devise管理下のコントローラーの紐付きが変更されているPrefix Verb URI Pattern Controller#Action cancel_user_registration GET /users/cancel(.:format) users/registrations#cancel new_user_registration GET /users/sign_up(.:format) users/registrations#new edit_user_registration GET /users/edit(.:format) users/registrations#edit user_registration PATCH /users(.:format) users/registrations#update PUT /users(.:format) users/registrations#update DELETE /users(.:format) users/registrations#destroy POST /users(.:format) users/registrations#create
-
registrations_controller.rb
を確認する
クラス名をみると、Devise::RegistrationsController
を継承していることがわかる。コメントアウトしている箇所はすでにDevise::RegistrationsController
で定義されているものusers/registrations_controller.rb
に同名のメソッドを定義することによって、コメントアウト部分を上書きすることができる。devise
の機能を継承しているので、定義されているメソッドの中のsuper
は、devise
で使用できるメソッドをそのまま実行することができる
ユーザー情報を登録するためのフォームを作成
- ユーザー情報登録画面へ遷移するnewアクションを設定するため、
registrations_controller.rb
を編集するapp/controllers/users/registrations_controller.rb# frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] def new @user = User.new end # 省略
- newアクションに対応するビューを整えるため、
app/views/devise/registrations/new.html.erb
を編集する<!--<h2>Sign up</h2>--> <h2>ユーザー情報登録</h2> <%= form_for(@user, url: user_registration_path) do |f| %> <%= render "devise/shared/error_messages", resource: @user %> <%#= name項目 %> <%#= 中略 %> <%#= Nextボタン %> <div class="actions"> <%= f.submit "Next" %> </div> <% end %> <%= render "devise/shared/links" %>
挙動を確認
ユーザー情報をsessionに保持
-
registrations_controller.rb
のcreateアクションを実装するapp/controllers/users/registrations_controller.rb# frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] def new @user = User.new end def create # ユーザー情報のバリデーションチェックをする @user = User.new(sign_up_params) unless @user.valid? render :new, status: :unprocessable_entity and return end # ユーザー情報をsessionに保持させる session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] # attributesメソッドでデータ整形をした際にパスワードの情報は含まれないため、パスワードを再度sessionに代入する必要がある # 次の住所情報登録で使用するインスタンスを生成する @address = @user.build_address # 住所情報登録ページへ遷移する render :new_address, status: :accepted end # 省略
住所情報を登録
- ルーティングを編集する
config/routes.rb
# 省略 # 住所情報を登録させるページを表示するnew_addressアクションと住所情報を登録するcreate_addressアクションのルーティングを設定 devise_scope :user do get 'addresses', to: 'users/registrations#new_address' post 'addresses', to: 'users/registrations#create_address' end root to: "home#index" end
- ビュー
app/views/devise/registrations/new_address.html.erb
を手動作成する -
app/views/devise/registrations/new_address.html.erb
を編集する<h2>住所情報登録</h2> <%= form_for @address, data: { turbo: false } do |f| %> <%= render "devise/shared/error_messages", resource: @address %> <div class="field"> <%= f.label :postal_code %><br /> <%= f.text_field :postal_code %> </div> <div class="field"> <%= f.label :address %><br /> <%= f.text_field :address %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>
挙動を確認
住所情報をDB保存
-
create_address
アクションで、ユーザー情報と住所情報全てをテーブルに保存するため、registrations_controller.rb
を編集するapp/controllers/users/registrations_controller.rb# 省略 def create # 省略 end def create_address # 住所情報のバリデーションチェック @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? render :new_address, status: :unprocessable_entity and return end # バリデーションチェックが完了した情報とsessionで保持していた情報を合わせ、ユーザー情報として保存 @user.build_address(@address.attributes) @user.save # sessionを削除 session["devise.regist_data"]["user"].clear # ログインをする sign_in(:user, @user) end private def address_params params.require(:address).permit(:postal_code, :address) end # 省略
-
app/views/devise/registrations/create_address.html.erb
を手動作成する -
app/views/devise/registrations/create_address.html.erb
を編集する<h2>登録が完了しました</h2> <%= link_to "トップへ戻る", root_path%>