LoginSignup
45
26

More than 1 year has passed since last update.

【この記事はもう古いので読まないこと!!】Turboを有効化したままRails 7.0でDeviseを使う方法

Last updated at Posted at 2023-01-04

この記事は古いです!!
Devise 4.9.0が正式リリースされたため、この記事の内容はすでに古くなっています。
Devise 4.9の導入方法は以下の記事で説明しています。ですので、こちらを読むようにしてください。
Devise 4.9をインストールしてRails 7.0 (Hotwire/Turbo)に対応する - Qiita

はじめに

Rails 7.0がリリースされて1年以上経ちましたが、Railsのメジャーな認証ライブラリであるDeviseは未だにRails 7.0に部分的にしか対応していません(参考)。
そのため、Rails 7.0でrails newしたRailsアプリケーション(つまり、Turboが有効になっている状態)だと、Deviseを使おうと思っても意図しない挙動になります。

そこで、この記事ではTurboを有効化したままRails 7.0でDeviseを使う方法を紹介します。

なお、ネットを検索するとこの記事と同じテーマで書かれた記事がすでにいくつもありますが、今回紹介する方法はかなりシンプルかつ、実際のサンプルアプリ(とテストコード)で動作確認済みなので、個人的にはかなりお勧めです。

対象バージョン

この記事は以下のバージョンで動作確認しています(それぞれ2023年1月4日時点での最新バージョンです)。

  • Rails 7.0.4
  • Devise 4.8.1
  • turbo-rails 1.3.2
  • Ruby 3.2.0

Deviseのバージョンに要注意!

将来的にDeviseがRails 7.0に対応した場合、この記事の内容はおそらく不要なものになります。
この記事を読み進める前に、以下のリンクを開いてDeviseの新しいバージョンがリリースされていないか確認しておきましょう。

参考文献

この記事は以下のリンクで紹介されていた対応例をベースにして、少し修正を加えたものです。
各コードの技術的な説明は以下のリンクを参考にしてください。

それでは以下が本編です。

手順

事前条件として、DeviseのREADMEのとおりにDeviseをRailsに組み込んだ状態を想定します。

まず、app/controllers/turbo_devise_controller.rb に以下のコードを追加します。

app/controllers/turbo_devise_controller.rb
class TurboDeviseController < ApplicationController
  class Responder < ActionController::Responder
    def to_turbo_stream
      if @default_response
        @default_response.call(options.merge(formats: :html))
      else
        controller.render(options.merge(formats: :html))
      end
    rescue ActionView::MissingTemplate => error
      if get?
        raise error
      elsif has_errors? && default_action
        if respond_to?(:error_rendering_options, true)
          # For responders 3.1.0 or higher
          render error_rendering_options.merge(formats: :html, status: :unprocessable_entity)
        else
          render rendering_options.merge(formats: :html, status: :unprocessable_entity)
        end
      else
        navigation_behavior error
      end
    end
  end

  self.responder = Responder
  respond_to :html, :turbo_stream
end

次に、config/initializers/devise.rbを開いて、TurboFailureAppクラスの定義を追加します。
場所は # frozen_string_literal: true のすぐ下あたりが良いでしょう。

config/initializers/devise.rb
# frozen_string_literal: true

class TurboFailureApp < Devise::FailureApp
  def respond
    if request_format == :turbo_stream
      redirect
    else
      super
    end
  end

  alias skip_format? is_navigational_format?
end

# Assuming you have not yet modified this file, each configuration option below
# (以下略)

さらに、config/initializers/devise.rbにある以下の3つの設定を変更します。

config/initializers/devise.rb
-  # config.parent_controller = 'DeviseController'
+  config.parent_controller = 'TurboDeviseController'
config/initializers/devise.rb
-  # config.navigational_formats = ['*/*', :html]
+  config.navigational_formats = ['*/*', :html, :turbo_stream]
config/initializers/devise.rb
-  # config.warden do |manager|
-  #   manager.intercept_401 = false
-  #   manager.default_strategies(scope: :user).unshift :some_external_strategy
-  # end
+  config.warden do |manager|
+    manager.failure_app = TurboFailureApp
+  end

ログアウトのリンクはmethod: :deleteではなく、data: { turbo_method: :delete }にします。

-<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
+<%= link_to 'ログアウト', destroy_user_session_path, data: { turbo_method: :delete } %>

アカウント削除のボタンはdata: { confrim: "Are you sure?" }ではなく、data: { turbo_confirm: "Are you sure?" }とします。
ただし、この変更はrails generate devise:views(devise-i18nを使っている場合はrails g devise:i18n:views)でDeviseのviewをカスタマイズ可能になっていることが前提です。

app/views/devise/registrations/edit.html.erb
-<%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>
+<%= button_to "Cancel my account", registration_path(resource_name), data: { turbo_confirm: "Are you sure?" }, method: :delete %>

手順は以上です。
これでTurboを有効にしたままRails 7.0でDeviseが使えるはずです!

動作検証用に作ったサンプルアプリ

この記事を書くにあたって、以下のサンプルアプリケーションで動作確認しています。
実際の動きやコード全体を見てみたい人はこちらを参考にしてください。

Screenshot 2023-01-04 at 17.52.40.png
Screenshot 2023-01-04 at 17.52.52.png
Screenshot 2023-01-04 at 17.53.02.png

テストコード

上記のサンプルアプリにはテストコードもあります。
テストコードでは以下の挙動をテストしています。

  • アカウント登録
  • ログイン・ログアウト
  • アカウント編集
  • パスワードリセット
  • アカウント削除

参考:OmniAuthでOAuth認証する場合

OmniAuthを利用して、TwitterやGitHubなどのOAuth認証する場合は認証ボタン(認証リンク)をlink_toではなくbutton_toに変更し、その上でTurboを無効化(data: { turbo: false }を指定)します。
僕が調べた限り、Turboを有効にしたまま認証ボタンを設置する方法はありませんでした。

app/views/devise/shared/_links.html.erb
-<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
+<%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br />

リンクがボタンになるので見た目が少し変わりますが、その点は必要に応じてCSSでお化粧してあげてください。

なお、Turboの対応とは無関係ですが、OmniAuth 2.0以上では omniauth-rails_csrf_protection gemを使って認証時にPOSTリクエストを送る必要があるので、こちらもお忘れなく。

45
26
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
26