やりたいこと
親ユーザーの認証ではデフォルトのemailとpasswordでの認証を使用してログイン。
子ユーザーの認証では、nicknameとパスワードで登録と認証ができるようにする。
また、子ユーザーを作成できるのは、親ユーザーのみ。
family(親ユーザー)
colum | validation |
---|---|
null:false | |
password | null:false |
family_name | null:false |
user(子ユーザ)
colum | validation |
---|---|
nickname | null:false |
password | null:false |
family_id | null:false |
手順
root用のコントロ3ーラーとビューを作成
rails g controller home index
class HomeController < ApplicationController
def index
end
end
rootを変更
作成したhome/indexをrootに変更。
Rails.application.routes.draw do
get 'home/index'
end
rails s
を実行してからlocalhost:3000
にアクセスして表示を確認する。
gemのインストール
Gemfileに追記
gem 'devise'
deviseをインストール
bundle install
bundle exec rails g devise:install
忘れがちなので注意
deviseの設定変更
Devise.setup do |config|
#省略
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
# config.scoped_views = true ←このコメントアウトをはずす
Devise.setup do |config|
#省略
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
config.scoped_views = true ←このコメントアウトをはずす
上記はスコープ付きビューが必要な場合に必要です。今回は結局いらなかったかも?
公式リファレンス
deviseのモデルの作成
以下のコマンドを実行してdeviseのmodelを作成。
rails g devise family
rails g devise user
作成できたらcolumの追加をする。
class DeviseCreateFamilies < ActiveRecord::Migration[6.0]
def change
create_table :families do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :family_name, null: false ←ここを追加
#省略
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :nickname, null: false, default: ""
t.integer :family_id, null: false
# 省略
family_idは最初reference型で参照していたが、うまくいかなかったのでintegerにして直接数値を持ってくることにした。
migratetionファイルの編集が完了したら忘れずにマイグレーションを行う。
rails db:migrate
rails db:migrate:status
でmigrateできているかの確認をおこなう。
ルーティングの修正
ルーティングを変更しないと
変更しないと、familyとuserの使うコントローラーがぶつかってしまうので以下に変更。
Rails.application.routes.draw do
get 'home/index'
devise_for :families, controllers: {
sessions: 'families/sessions',
passwords: 'families/passwords',
registrations: 'families/registrations'
}
devise_for :users, controllers: {
sessions: 'users/sessions',
passwords: 'users/passwords',
registrations: 'users/registrations'
}
root to: 'home/index'
end
これでルーティングが以下のようになる
Prefix Verb URI Pattern Controller#Action
home_index GET /home/index(.:format) home#index
new_family_session GET /families/sign_in(.:format) families/sessions#new
family_session POST /families/sign_in(.:format) families/sessions#create
destroy_family_session DELETE /families/sign_out(.:format) families/sessions#destroy
new_family_password GET /families/password/new(.:format) families/passwords#new
edit_family_password GET /families/password/edit(.:format) families/passwords#edit
family_password PATCH /families/password(.:format) families/passwords#update
PUT /families/password(.:format) families/passwords#update
POST /families/password(.:format) families/passwords#create
cancel_family_registration GET /families/cancel(.:format) families/registrations#cancel
new_family_registration GET /families/sign_up(.:format) families/registrations#new
edit_family_registration GET /families/edit(.:format) families/registrations#edit
family_registration PATCH /families(.:format) families/registrations#update
PUT /families(.:format) families/registrations#update
DELETE /families(.:format) families/registrations#destroy
POST /families(.:format) families/registrations#create
new_user_session GET /users/sign_in(.:format) users/sessions#new
user_session POST /users/sign_in(.:format) users/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) users/sessions#destroy
new_user_password GET /users/password/new(.:format) users/passwords#new
edit_user_password GET /users/password/edit(.:format) users/passwords#edit
user_password PATCH /users/password(.:format) users/passwords#update
PUT /users/password(.:format) users/passwords#update
POST /users/password(.:format) users/passwords#create
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
families GET /families(.:format) families#index
family GET /families/:id(.:format) families#show
root GET / home#index
deviseのビューの作成
rails g devise:views families
rails g devise:views users
コマンドでviewを作成
コントローラーを作成
次のコマンドでコントローラーを作成
bundle exec rails generate devise:controllers users
bundle exec rails generate devise:controllers families
いったん表示されるか確認
localhost:3000/users/sign_in
localhost:3000/families/sign_in
でそれぞれのページが表示されるか確認する。
viewファイルの編集
今回userでの登録はnicknameとパスワードのみで行いたい。
そのためフォームをいじる。
<%= form_with model: @user, url: user_registration_path, method: :post, local: true do |f| %>
<div class="field">
<%= f.label :nickname, "ユーザー名" %><br />
<%= f.text_field :nickname, id:"nickname" %>
</div>
<div class="field">
<%= f.label :password, "パスワード(6文字以上)" %><br />
<%= f.password_field :password, id:"family_password", autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation, "パスワード再入力" %><br />
<%= f.password_field :password_confirmation, id:"family_password_confirmation", autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "新規登録", class: :form__btn %>
</div>
<% end %>
こんな感じにしておく。
また、familyの登録には、family_nameを加えたいので、viewをいじる
<%= render "families/shared/error_messages", resource: resource %>
<%= form_with model: @family, url: family_registration_path, method: :post, local: true do |f| %>
<div class="field">
<%= f.label :email, "保護者メールアドレス" %><br />
<%= f.email_field :email, id:"family_email", autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password, "パスワード(6文字以上)" %><br />
<%= f.password_field :password, id:"family_password", autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation, "パスワード再入力" %><br />
<%= f.password_field :password_confirmation, id:"family_password_confirmation", autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :family_name, "家族名" %><br />
<%= f.text_field :family_name, id:"family_name" %>
</div>
<div class="actions">
<%= f.submit "新規登録", class: :form__btn %>
</div>
<% end %>
スタイルシートとかいろいろいじっていい感じにするのに時間がかかったけどそこは、本件と関係ないのではしょる。
modelファイルの編集
まずはfamilyのモデルから
class Family < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:authentication_keys => [:email]
validates :family_name, presence: true
has_many :users, dependent: :destroy
end
:authentication_keys => [:email]
を追記して、familyモデルではemailでの認証のままにしておく。(書かなくてもいいと思って、書かないでやったらエラーが発生したので、カスタムする場合はそれぞれのモデルに書いた方がよさそうです。)
また、has_many :users, dependent: :destroy
でuserモデルとアソシエーションを記述してfamilyに対してuserを紐づけておく。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:authentication_keys => [:nickname]
validates :nickname, presence: true, uniqueness: true
belongs_to :family
def email_required?
false
end
def email_changed?
false
end
end
:authentication_keys => [:nickname]
で認証キーを[:nickname]にオーバーライドします。
nicknameで認証するために一意のバリデーションをかけておきます。
belongs_to :family
でアソシエーションを行い、familyと紐づけておきます。
def email_required?
false
end
def email_changed?
false
end
emailをキーにしていないため、データの更新や変更にemailを必須とするとエラーの原因になるためバリデーションを無効化させています。
コントローラーの編集
family_nameが受け取れるように、before_actionでpramaterの要素を追加してあげる。
before_action :configure_sign_up_params, only: [:create]
private
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, :family_name])
end
end
family次にログインの実装のためにsession_contoroller.rbファイルを編集する。
class Families::SessionsController < Devise::SessionsController
before_action :configure_sign_in_params, only: [:create]
protected
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_in, keys: [:email])
end
end
emailが認証キーになるように記述。デフォルト設定のままなので必要ないと思ったが、なぜかemailがpermitできないエラーが出てしまったので、記述した。記述することでエラーが解消された。
次にuserコントローラーの設定
class Users::RegistrationsController < Devise::RegistrationsController
before_action :authenticate_family!, if: :devise_controller?
before_action :configure_sign_up_params, only: [:create]
def new
authenticate_family!
@user = User.new
end
# ユーザーを登録するアクション
def create
authenticate_family!
super do |user|
if family_signed_in?
user.family = current_family
user.save!
end
end
end
private
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :password, :password_confirmation, :family_id])
end
def sign_up(resource_name, resource)
if !current_family
sign_in(resource_name, resource)
end
end
end
before_action :authenticate_family!, if: :devise_controller?
これによりfamilyでログインしていない場合userの新規登録ができないようにしています。
before_action :configure_sign_up_params, only: [:create]
private
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :password, :password_confirmation, :family_id])
end
これにより、:family_idと:nicknameを受け取ることが可能になり、emailは不要となる。
def new
authenticate_family!
@user = User.new
end
# ユーザーを登録するアクション
def create
authenticate_family!
super do |user|
if family_signed_in?
user.family = current_family
user.save!
end
end
end
authenticate_family!でfamilyで認証していないとアクションができないようにした。
paramaterをカスタマイズしたのでアクションの記述をオーバーライドした。createアクションは、superで元の処理を使いつつ、family_idをどうしてもparamsに含むことができずにエラーが出てしまったので、追加した。
class Users::SessionsController < Devise::SessionsController
before_action :configure_sign_in_params, if: :devise_controller?
def create
super
end
protected
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_in, keys: [:nickname, :password, :remember_me])
end
end
sessionのparamsに[:nickname]が含まれるようにするためにbefore_actionで記述。
これで無事に実装完了しました。
終わりに
まとめて書くとこんだけかと思いますが、これを実装するまでに1日丸々費やしました。
間違いや、解釈違いも多いかと思いますので、指摘していただけたらと思います。これから実装する人の一助になれば幸いです。
参考にした記事など