ポートフォリオ用のWebサイトに必須と言われるゲストログイン機能。
特にDevise
のconfirmable
を使用している場合は,ゲストログイン機能が無いと,新規登録が面倒という理由でポートフォリオを見てすらもらえない可能性が高くなります。
ところが,普通のWebサイトには導入しないためか,記事が見当たりませんでした。また,ゲストユーザー機能が動作しなくなるリスクを抱えた実装も複数見かけました。そこで,私のたどりついた実装方法をアップさせていただきます。
以下,Devise
を使用している前提とし,トップページが root 'homes#index' で設定されていることとします。
Rails.application.routes.draw do
# トップページは homes#index の前提
root "homes#index"
end
YouTube
- YouTubeに解説動画をアップしました
その1: ゲストログイン機能の実装方法(簡易版)
# 以下を追加
post '/homes/guest_sign_in', to: 'homes#guest_sign_in'
# 以下を追加
def guest_sign_in
user = User.find_or_create_by!(email: 'guest@example.com') do |user|
user.password = SecureRandom.urlsafe_base64
# user.skip_confirmation! # Confirmable を使用している場合は必要
# 例えば name を入力必須としているならば, user.name = "ゲスト" なども必要
end
sign_in user
redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
end
# 以下を追加
<%= link_to 'ゲストログイン(閲覧用)', homes_guest_sign_in_path, method: :post %>
-
ポイントは
Devise
のsign_in
メソッドを利用することです。 -
find_by
ではなく,find_or_create_by
を利用しています。これにより,ゲストユーザーをあらかじめ作成する手間を省けます。また,ゲストユーザーを削除されてゲスト機能が動作しなくなるリスクも回避できます。 -
パスワードを特定されると,ユーザー編集ページからメールアドレス・パスワードを変更される可能性があるため,パスワードはランダム文字列にしています。
-
バリデーションの影響でゲストユーザーを作成できない場合は,エラーを発生させるように設定しております。
その2: ゲストログイン機能の実装方法
上記の実装方法は理解しやすいものの,ゲストログイン機能をHomesController
に任せることには違和感があります。本来は,ログイン機能同様,Devise
のSessionsController
に任せるべきでしょう。
例えば次のように実装すると,より自然なものになるのではないでしょうか。
2-1. ゲストログイン機能の設定
- まず,
SessionsController
に新しいアクションguest_sign_in
を準備します。
# 以下を追加
devise_scope :user do
post 'users/guest_sign_in', to: 'users/sessions#guest_sign_in'
end
- アクション
guest_sign_in
を設定するため,app/controllers
にusers
ディレクトリを作成し,その中に次のsessions_controller.rb
を作成します。ゲストユーザーを探す or 作成する機能はUser.rb
に移動させるとコントローラーがスッキリします。
class Users::SessionsController < Devise::SessionsController
def guest_sign_in
user = User.guest
sign_in user
redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
end
end
# 以下を追加
def self.guest
find_or_create_by!(email: 'guest@example.com') do |user|
user.password = SecureRandom.urlsafe_base64
# user.confirmed_at = Time.now # Confirmable を使用している場合は必要
# 例えば name を入力必須としているならば, user.name = "ゲスト" なども必要
end
end
- トップページにゲストログインボタンを用意する場合は,以下を追加すればOKです。(スタイルは各自で追加して下さい)
# 以下を追加
<%= link_to 'ゲストログイン(閲覧用)', users_guest_sign_in_path, method: :post %>
2-2. ゲストユーザーを削除できないようにする
上記の実装方法ならば,仮にゲストユーザーを削除されたとしても,ゲスト機能が動作しなくなることはありません。
ですが,例えば2名の方が同時にログインされている状態で,片方の方がゲストユーザーを削除しますと,もう片方の方も強制的にログアウトさせられてしまいます。ポートフォリオの場合はレアケースだと思いますが,念のためゲストユーザーを削除できないように設定しておきましょう。
- ゲストユーザーが削除機能を使用できないようにするには,
registrations.rb
を編集する必要があります。まずは,ルーティングを変更します。
# devise_for :users を次に置き換える
devise_for :users, controllers: {
registrations: 'users/registrations'
}
- destroyアクションの動作前に,メールアドレスがゲストユーザー用になっていないかチェックするように設定します。
- ゲストユーザーならばフラッシュを出した上でトップページにリダイレクトさせるように設定しています。
class Users::RegistrationsController < Devise::RegistrationsController
before_action :ensure_normal_user, only: :destroy
def ensure_normal_user
if resource.email == 'guest@example.com'
redirect_to root_path, alert: 'ゲストユーザーは削除できません。'
end
end
end
2-3 ゲストユーザーがメールアドレス・パスワードを変更できないようにする
上記の実装ならば,「ユーザー編集機能」や「パスワード再設定機能」によりメールアドレス
・パスワード
を変更される可能性は非常に低いですし,仮に変更されたとしてもポートフォリオならば問題にならないかと思います。
それでも,「ゲストユーザーのメールアドレス・パスワードを絶対に変更されたくない!」という場合は,更に次のような設定をすればOKです。
- 削除機能を止めるのと同じ手法で,ゲストユーザーがメールアドレス・パスワードを編集できないように設定します。
- before_action :ensure_normal_user, only: :destroy
+ before_action :ensure_normal_user, only: %i[update destroy]
- redirect_to root_path, alert: 'ゲストユーザーは削除できません。'
+ redirect_to root_path, alert: 'ゲストユーザーの更新・削除はできません。'
- パスワード再設定メールの送信機能を止めるには,
passwords_controller.rb
のcreate
アクションの動作前にチェックすればOKです。まずはルーティングを変更します。
# devise_for :users, controllers: {
# registrations: 'users/registrations'
# }
# を次に置き換える。(,の付け忘れに注意!)
devise_for :users, controllers: {
registrations: 'users/registrations',
passwords: 'users/passwords'
}
- パスワード再設定ページのフォームに入力されたメールアドレスは
params[:user][:email]
で受け取れるので,これを利用してゲストユーザーを特定します。- メールアドレスは大文字が小文字に変換されて保存されているため,
downcase
メソッドが必要です。
- メールアドレスは大文字が小文字に変換されて保存されているため,
class Users::PasswordsController < Devise::PasswordsController
before_action :ensure_normal_user, only: :create
def ensure_normal_user
if params[:user][:email].downcase == 'guest@example.com'
redirect_to new_user_session_path, alert: 'ゲストユーザーのパスワード再設定はできません。'
end
end
end
備考
-
上記はあくまで最低限度の実装です。例えば,ゲストユーザーの情報は他の利用者にも引き継がれますので,必要があれば
Trackable
を入れて,current_sign_in_at
を基準に次のような実装をしてもよいかもしれません。- 定期的にデータを初期化する設定を入れる
- ゲストユーザーを複数用意して,現在ログイン日時の最も古いゲストユーザーでログインする
-
実は,最初,
devise/sessions/new.html.erb
の真似をして,hidden_field
を加えて……という手順で実装しようとしました。ところが,この方法ではuser_email
,user_password
などのidセレクタが重複するエラーが出てしまいます。そこで,sign_in
メソッドの存在を思い出し,このような実装方法にたどりつきました。