簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)


ポートフォリオ用のWebサイトに必須と言われるゲストログイン機能

特にDeviseconfirmableを使用している場合は,ゲストログイン機能が無いと,新規登録が面倒という理由でポートフォリオを見てすらもらえない可能性が高くなります。

ところが,普通のWebサイトには導入しないためか,記事が見当たりませんでした。また,ゲストユーザー機能が動作しなくなるリスクを抱えた実装も複数見かけました。そこで,私のたどりついた実装方法をアップさせていただきます。

以下,Deviseを使用している前提とし,トップページが root 'homes#index' で設定されていることとします。

routes.rb
Rails.application.routes.draw do
  # トップページは homes#index の前提
  root "homes#index"
end

YouTube

その1: ゲストログイン機能の実装方法(簡易版)

config/routes.rb
# 以下を追加
  post '/homes/guest_sign_in', to: 'homes#guest_sign_in'
app/controllers/homes_controller.rb
# 以下を追加
  def guest_sign_in
    user = User.find_or_create_by!(email: '[email protected]') do |user|
      user.password = SecureRandom.urlsafe_base64
      # user.confirmed_at = Time.now  # Confirmable を使用している場合は必要
      # 例えば name を入力必須としているならば, user.name = "ゲスト" なども必要
    end
    sign_in user
    redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
  end
app/views/homes/index.html.erb
# 以下を追加
<%= link_to 'ゲストログイン(閲覧用)', homes_guest_sign_in_path, method: :post %>
  • ポイントはDevisesign_inメソッドを利用することです。

  • find_byではなく,find_or_create_byを利用しています。これにより,ゲストユーザーをあらかじめ作成する手間を省けます。また,ゲストユーザーを削除されてゲスト機能が動作しなくなるリスクも回避できます。

  • パスワードを特定されると,ユーザー編集ページからメールアドレスパスワードを変更される可能性があるため,パスワードはランダム文字列にしています。

  • バリデーションの影響でゲストユーザーを作成できない場合は,エラーを発生させるように設定しております。

その2: ゲストログイン機能の実装方法

上記の実装方法は理解しやすいものの,ゲストログイン機能をHomesControllerに任せることには違和感があります。本来は,ログイン機能同様,DeviseSessionsControllerに任せるべきでしょう。

例えば次のように実装すると,より自然なものになるのではないでしょうか。

2-1. ゲストログイン機能の設定

  • まず,SessionsControllerに新しいアクションguest_sign_inを準備します。
config/routes.rb
# 以下を追加
  devise_scope :user do
    post 'users/guest_sign_in', to: 'users/sessions#guest_sign_in'
  end
  • アクションguest_sign_inを設定するため,app/controllersusersディレクトリを作成し,その中に次のsessions_controller.rbを作成します。ゲストユーザーを探す or 作成する機能は User.rb に移動させるとコントローラーがスッキリします。
app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def guest_sign_in
    user = User.guest
    sign_in user
    redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
  end
end
app/models/user.rb
# 以下を追加
  def self.guest
    find_or_create_by!(email: '[email protected]') do |user|
      user.password = SecureRandom.urlsafe_base64
      # user.confirmed_at = Time.now  # Confirmable を使用している場合は必要
      # 例えば name を入力必須としているならば, user.name = "ゲスト" なども必要
    end
  end
  • トップページにゲストログインボタンを用意する場合は,以下を追加すればOKです。(スタイルは各自で追加して下さい)
app/views/homes/index.html.erb
# 以下を追加
<%= link_to 'ゲストログイン(閲覧用)', users_guest_sign_in_path, method: :post %>

2-2. ゲストユーザーを削除できないようにする

上記の実装方法ならば,仮にゲストユーザーを削除されたとしても,ゲスト機能が動作しなくなることはありません。

ですが,例えば2名の方が同時にログインされている状態で,片方の方がゲストユーザーを削除しますと,もう片方の方も強制的にログアウトさせられてしまいます。ポートフォリオの場合はレアケースだと思いますが,念のためゲストユーザーを削除できないように設定しておきましょう。

  • ゲストユーザーが削除機能を使用できないようにするには,registrations.rbを編集する必要があります。まずは,ルーティングを変更します。
config/routes.rb
# devise_for :users を次に置き換える
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  • destroyアクションの動作前に,メールアドレスがゲストユーザー用になっていないかチェックするように設定します。
    • ゲストユーザーならばフラッシュを出した上でトップページにリダイレクトさせるように設定しています。
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :ensure_normal_user, only: :destroy

  def ensure_normal_user
    if resource.email == '[email protected]'
      redirect_to root_path, alert: 'ゲストユーザーは削除できません。'
    end
  end

end

2-3 ゲストユーザーがメールアドレス・パスワードを変更できないようにする

上記の実装ならば,「ユーザー編集機能」や「パスワード再設定機能」によりメールアドレスパスワードを変更される可能性は非常に低いですし,仮に変更されたとしてもポートフォリオならば問題にならないかと思います。

それでも,「ゲストユーザーのメールアドレス・パスワードを絶対に変更されたくない!」という場合は,更に次のような設定をすればOKです。

  • 削除機能を止めるのと同じ手法で,ゲストユーザーがメールアドレス・パスワードを編集できないように設定します。
app/controllers/users/registrations_controller.rb
- 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.rbcreateアクションの動作前にチェックすればOKです。まずはルーティングを変更します。
config/routes.rb
#   devise_for :users, controllers: {
#     registrations: 'users/registrations'
#   }
# を次に置き換える。(,の付け忘れに注意!)
  devise_for :users, controllers: {
    registrations: 'users/registrations',
    passwords: 'users/passwords'
  }
  • パスワード再設定ページのフォームに入力されたメールアドレスはparams[:user][:email]で受け取れるので,これを利用してゲストユーザーを特定します。
    • メールアドレスは大文字が小文字に変換されて保存されているため,downcaseメソッドが必要です。
app/controllers/users/passwords_controller.rb
class Users::PasswordsController < Devise::PasswordsController
  before_action :ensure_normal_user, only: :create

  def ensure_normal_user
    if params[:user][:email].downcase == '[email protected]'
      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メソッドの存在を思い出し,このような実装方法にたどりつきました。

サンプルコード

  • 【GitHub】devise-bootstrap-sample

    • masterブランチは,ゲストログイン機能を付ける前の状態です。
    • ゲストログイン機能のサンプルコードは,guest_login_functionブランチをご覧下さい。
$ git clone -b guest_login_function https://github.com/T-Tsujii/devise_sample.git