どうした?
前回まででモデルとルーティングを設定し、コントローラとビューファイルを作成しました。
今回はコントローラとビューファイルを作り込んで、
- ユーザー作成
- ユーザー編集
- ログイン
- ログアウト
ができるようにします。
今回の目標
ユーザー情報を操作するため、users/resistrations_controller
に
- new
- create
- edit
- update
アクションを実装します。
ログイン情報を操作するため、users/sessions_controller
に
- new
- create
- delete
アクションを実装します。
手順
ユーザー作成
ビューファイルの作成
まずはユーザー情報を入力するビューファイルを作成していきましょう。
ユーザーの作成と編集は同じレイアウトなので、部分テンプレートを使用します。
views/users/resistrations
フォルダ内に_user_form.html.erb
ファイルを作成します。
前回作成した空のnew.html.erb
ファイルで部分テンプレートを呼び出します。
<%= render 'user_form' %>
_user_form.html.erb
を以下のように編集します。
<%= form_with model: @user do |f| %>
<div class="my-3">
<%= f.label :name, class: 'form-label fw-bold' %>
<%= f.text_field :name, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.label :email, class: 'form-label fw-bold' %>
<%= f.text_field :email, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.label :password, class: 'form-label fw-bold' %>
<%= f.password_field :password, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.label :password_confirmation, class: 'form-label fw-bold' %>
<%= f.password_field :password_confirmation, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.submit '登録', class: "btn btn-primary" %>
</div>
<% end %>
コントローラの作成
Deviseで自動作成したコントローラには基本的なCRUDアクションといくつかのメソッドが定義されています。
まずはアクションの中身を見てみましょう。
# GET /resource/sign_up
def new
super
end
全てのアクションがsuper
、つまり親クラスであるDevise::RegistrationsController
を継承していますね。
この親クラスを見てみると、、
うーん、、、分かるようで分からないような、、、
何だかあちこちから変数を引っ張って来て便利なようにしてくれている風ですが、僕がしたいのはもっとシンプルなものです。
new
アクションはギリギリ理解できたとしても、create
やupdate
などを完全に乗りこなせる自信がありません。
なので、super
は消してしまいましょう。
僕がnew
アクションでしたいことは、ただ1つ。これだけです。
def new
@user = User.new
end
コントローラ内のprotected
以下を見てみると、いくつかのメソッドが定義されています。
これらはストロングパラメータの設定に必要なようです。
モデルを作成したとき、Deviseが初めから用意していたカラムを思い出してください。
emailとpassword関連ですね。
それ以外のカラムをストロングパラメータで送信する(今回はname
)場合にkeys: [:attribute]
の部分を編集するようです。
、、、う〜ん、どこかでエラーが起きそう。
なので消します。
結局、コントローラは以下のようになりました。
class Users::RegistrationsController < Devise::RegistrationsController
# before_actionも削除
def new
@user = User.new
end
def create
@user = User.new(create_users_params)
if @user.save
sign_in(@user)
redirect_to # 任意のパス
else
render 'new'
end
end
# edit, updateは後述
private
def create_users_params
params.require(:user).permit(:name, :email, :password)
end
end
Deviseに慣れたプロフェッショナルはsuper
やメソッドを使いこなすのかもしれませんが、現在の僕はこれで精一杯です。
create
アクション内でsign_in
メソッドを使用しています。
これはDeviseで用意されているメソッドで、認証済のユーザーをログインさせるのに便利です。
sign_in(@user)
を与えることで@user
に対して
- ログイン
- セッションの保持
-
/sign_up
,/login
へのアクセス不可
をしてくれます。
試しに、画面上でサンプルユーザーを1人登録してみましょう。
登録後、コンソールでユーザーが増えていることが確認できたら実装成功です!
ユーザー編集
続いて、ユーザー情報を編集できるようにします。
ビューファイルの作成
edit.html.erb
ファイルにて先程作成した部分テンプレートを呼び出します。
<%= render 'user_form' %>
コントローラの設定
まずは、アクション内でやりたいことを整理します。
edit
アクション
new
と同じように、super
を削除し、現在のユーザーを取得します。
update
アクション
edit
と同じように、super
を削除し、現在のユーザーを取得します。
現在のユーザーに対してupdate
メソッドに渡されたparams
情報で更新するようにします。
ユーザー情報の編集では、パスワードの入力を要求しないようにします。
Deviseはデフォルトでパスワードを更新するとログアウトする仕様になっています。
これをカスタマイズするために
-
update_resource
メソッドをオーバーライドする - Userモデルに
update_without_current_password
を定義する -
update_users_params
をストロングパラメータに設定する
という手順をとります。
update_resource
メソッドをオーバーライドする
Deviseはデフォルトでupdate_resource
というメソッドを持ち合わせています。
このメソッドはユーザー情報の更新の際にパスワードを更新するとログアウトする仕様となっています。
そのため、コントローラにてメソッドをオーバーライドする必要があります。
private
def update_resource(resource, params)
resource.update_without_current_password(params)
end
Userモデルにupdate_without_current_password
を定義する
上でオーバーライドしたコードの中でupdate_without_current_password
を使用しているので、これをUserモデルで定義します。
def update_without_current_password(params)
if params[:password].blank? && params[:password_confirmation].blank?
params.delete(:password)
params.delete(:password_confirmation)
end
update(params)
end
if文内ではpassword
とpassword_confirmation
の両方が空欄のとき、送信するparamsハッシュからそれぞれの存在を削除(nil)しています。
password
とpassword_confirmation
を空欄(blank)ではなく、そもそもそんなキーはない(nil)ことにしてしまうという手法です。
(blankだとバリデーションエラーになるが、nilならストロングパラメータに引っかからない限りparamsとして通すイメージ)
update_users_params
をストロングパラメータに設定する
create
とupdate
では送信するカラムが異なるため、それぞれに対してストロングパラメータを定義します。
update_users_params
は以下のようになります。
def update_users_params
params.require(:user).permit(:name, :email)
end
以上をまとめると、ユーザー情報の更新は以下のようになります。
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(update_users_params)
redirect_to categories_path, notice: "ユーザー「#{@user.name}」を更新しました。"
else
render 'edit'
end
end
private
# create_users_paramsは先述
def update_users_params
params.require(:user).permit(:name, :email)
end
def update_resource(resource, params)
resource.update_without_current_password(params) # update_without_current_passwordはUserモデルで定義
end
ログイン
続いて、ログイン機能です。
ビューファイルの作成
views/users/sessions
フォルダ内にnew.html.erb
ファイルを以下のように作成しました。
<%= form_with url: login_path, scope: :session do |f| %>
<div class="my-3">
<%= f.label :email, class: 'form-label fw-bold' %>
<%= f.text_field :email, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.label :password, class: 'form-label fw-bold' %>
<%= f.password_field :password, class: 'container-fluid' %>
</div>
<div class="my-3">
<%= f.submit 'ログイン', class: "btn btn-primary" %>
</div>
<% end %>
コントローラの作成
users/sessions_controller#create
では、ユーザー作成時に使用したsign_in
メソッドを使用します。
def new
end
def create
@user = User.find_by(email: params[:session][:email])
if @user.valid_password?(params[:session][:password])
sign_in(@user)
redirect_to # 任意のパス
else
render 'new', notice: "ログインに失敗しました"
end
end
ここでは、まず入力されたemail
を持つユーザーを探し、
valid_password?
というDevise標準のメソッドを使用してユーザーが持つパスワードと入力されたパスワード情報が一致するかを調べています。
email
とpassword
が一致したらsign_in
メソッドでユーザーをログインさせます。
ログイン機能はこれで実装完了です。
ログアウト
ログアウトリンクの作成
ログアウトリンクは以下のように作成しました。
<%= link_to('ログアウト', logout_path, method: :delete) if user_signed_in? %>
ここでは、user_signed_in?
というメソッドを使用して条件分岐させています。
このメソッドはDeviseで標準で用意されているメソッドで、セッション情報にユーザーがいるかどうかを調べます。
ちなみにDeviseはcurrent_user
も標準で用意しており、current_user.name
なども使用可能です。
コントローラの作成
ログアウト機能は、セッションから現在のユーザーを削除する機能です。
これはDeviseでも同様におこなっているので、super
を利用してしまいましょう。
def destroy
super
end
ログアウト機能はこれで実装完了です。
まとめ
以上で、Deviseをカスタマイズして使いたい!!シリーズを完結したいと思います。
Deviseは便利(に思える)一方、中身を分からずに使用する恐怖感もあると思います。
本記事が少しでもDeviseの導入の参考になれば嬉しいです!
参考
スペシャルサンクス
本記事を書きながら
- Deviseのカスタマイズなんて需要あるんかな、、?
- スクールに通ってる人はみんな勉強済みなのでは、、?
と思っていた僕に、「未経験でソースコードを深堀りするなんて凄いね」と言って下さった現役エンジニアの一言でやる気が起きました。
ありがとうございました!
また、users/resistrations_controller#update
の実装に沼った際にQiitaの質問記事で沢山の方にお助けいただきました。
ありがとうございました!