Rails ゲストログイン機能

# はじめに
今回はゲストログイン機能について学習していきたいと思います。

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

## 流れ
①ゲストユーザーをデータベースから取り出す
②そのユーザーをログインさせる
③フラッシュメッセージを出してトップページへリダイレクト
#1.ゲストログイン機能の実装方法（簡易版）

ゲストログイン用のアクションを作成

```config/routes.rb
# 以下を追加
  post '/homes/guest_sign_in', to: 'homes#new_guest'
```

```app/controllers/homes_controller.rb
# 以下を追加
  def new_guest
    user = User.find_or_create_by!(email: 'guest@example.com') do |user|
      user.password = SecureRandom.urlsafe_base64
      # user.confirmed_at = Time.now  # Confirmable を使用している場合は必要
    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 %>
```

ポイントはDeviseのsign_inメソッドを利用することです。

* find_byではなく，find_or_create_byを利用しています。これにより，ゲストユーザーをあらかじめ作成する手間を省けます。また，ゲストユーザーを削除されてゲスト機能が動作しなくなるリスクも回避できます。（コードではゲストユーザーがあればそれを取り出す。なければ新しく作成する設定をしている）

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

```
user.password = SecureRandom.urlsafe_base64
```

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

  * 例えば， name が必須であれば，user.name = "take" などを追加して下さい

#2.ゲストログイン機能の実装方法

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

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

## ゲストログイン機能の設定
まず，`SessionsController`に新しいアクション`new_guest`を準備します。

```config/routes.rb
# 以下を追加
  devise_scope :user do
    post 'users/guest_sign_in', to: 'users/sessions#new_guest'
  end
```
アクションnew_guestを設定するため，`app/controllers`に`users`ディレクトリを作成し，その中に次の`sessions_controller.rb`を作成します。ゲストユーザーを探す or 作成する機能は`User.rb`に移動させるとコントローラーがスッキリします。

```app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new_guest
    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: 'guest@example.com') do |user|
      user.password = SecureRandom.urlsafe_base64
      # user.confirmed_at = Time.now  # Confirmable を使用している場合は必要
    end
  end
```

トップページにゲストログインボタンを用意する場合は，以下を追加すればOKです。（スタイルは各自で追加して下さい）

```app/views/homes/index.html.erb
# 以下を追加
<%= link_to 'ゲストログイン（閲覧用）', users_guest_sign_in_path, method: :post %>
```

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

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

ゲストユーザーが削除機能を使用できないようにするには，`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 :check_guest, only: :destroy

  def check_guest
    if resource.email == 'guest@example.com'
      redirect_to root_path, alert: 'ゲストユーザーは削除できません。'
    end
  end

end
```

## ゲストユーザーがパスワードやメールアドレスを編集できないようにする

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

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

削除機能を止めるのと同じ手法で，ゲストユーザーがメールアドレス・パスワードを編集できないように設定します。

```app/controllers/users/registrations_controller.rb
- before_action :check_guest, only: :destroy
+ before_action :check_guest, only: %i[update destroy]  ＃変更


- redirect_to root_path, alert: 'ゲストユーザーは削除できません。'
+ redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。'　　＃変更
```

パスワード再設定メールの送信機能を止めるには，`passwords_controller.rb`の`create`アクションの動作前にチェックすれば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 :check_guest, only: :create

  def check_guest
    if params[:user][:email].downcase == 'guest@example.com'
      redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。'
    end
  end
end
```

【補足】 check_guestがほぼ同じ内容ですので，次のようにまとめてしまってもOKです。


```app/controllers/application_controller.rb
# 次を追加
# registrations_controller.rb と passwords_controller.rb の check_guest は削除
  def check_guest
    email = resource&.email || params[:user][:email].downcase
    if email == 'guest@example.com'
      redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。'
    end
  end
```

# 最後に
一応これでゲストログイン機能の完成です。
何か間違っているところがあればご教授していただけると幸いです。