[rails7] 初めてdeviseを導入する際につまずいたこと

はじめに
------
未経験、独学でPFを作成中で、Device gem が今のRailsログインシステムのデファクトスタンダートになっていると知り、導入しました。
しかし、Rails 7.0にまだ完全には対応していないよう([参考](https://github.com/heartcombo/devise/blob/main/CHANGELOG.md#481---2021-12-16))なのでエラーが起き、原因と解決策を忘れないためにこの記事を書きました。

例).
   - 新規登録(sign_up)時に「Undefined method ‘user_url’」エラー発生
   - 新規登録(sign_up)時のバリデーションエラー時にメッセージが表示されない

実行環境
----
この記事は以下の実行環境で動作確認しました。

- Rails 7.0.4.1
- Ruby 3.0.4
- Devise 4.8.1
- turbo-rails 1.1.1

注意事項
---
この情報は2023年1月24日時点の情報です。時間が経てば修正されるかもしれません。
 Web制作勉強中の者が書いているため、間違えているかもしれません。コメント等で指摘していただけると幸いです。

参考にさせていただいた記事
---
https://hotwired.dev/
https://turbo.hotwired.dev/handbook/introduction
https://qiita.com/jnchito/items/5c41a7031404c313da1f
https://qiita.com/jnchito/items/48db78c465493837c41f
https://kobacchi.com/rails7-devise-responded-to-turbo/

なぜ正しく機能しないのか
---
Rails 7.0版よりJavaScriptの送信に関する処理などが書かれたライブラリであるrails-ujsが推奨ではなくなり、rails newで環境を構築すると、
Hotwire(Turbo)が代わりに使われるようになりました。

Turboによって、モダンなウェブアプリケーションをたくさんのJavaScriptなしで、JSONの代わりにHTMLを送って作ることができる(jQueryを使わない)ようになり、サーバーサイドでSPA化をできるようになりました。
しかし、2021年12月にバージョン7になったこともあり、まだ互換性が低いgemがあるようです。


解決策
---
Deviseはインストール済みとします。
基本的にはこちらの記事に従って作業を進めます。
　https://qiita.com/jnchito/items/48db78c465493837c41f

 ### ・　「Undefined method ‘user_url’」エラーへの対処

Rails7ではデフォルトでTurbo Streamが有効になっているため、Deviseでsign_upを実行しようとすると、「Undefined method ‘user_url’」エラーが発生するようです。
config/initializers/devise.rbに追記します。
```rb:config/initializers/devise.rb
- #config.navigational_formats = ['*/*', :html]
+ config.navigational_formats = ['*/*', :html, :turbo_stream]
```


### ・　form_for を form_withに置き換える
Deviseで生成したViewのフォームヘルパーにはform_forが用いられていますが、Rails5.1以降はform_forとform_tagはform_withに統合されたため、form_withの利用が推奨されています（form_forは非推奨）。


 - views/devise/registrations/new.html.erb
 - views/devise/registrations/edit.html.erb
 - views/devise/sessions/new.html.erb
 - views/devise/passwords/new.html.erb
 - views/devise/passwords/edit.html.erb
 - views/devise/confirmations/new.html.erb

registrations/new.html.erbおよびregistrations/edit.html.erbの場合は以下のように変更します。
```rb:views/devise/registrations/new.html.erb
# 変更前
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
 ↓↓↓
# 変更後
= form_with model: @user, url: registration_path(resource_name), local: true do |f|
```
sessions/new.html.erbの場合は以下のように変更します。
```rb:views/devise/sessions/new.html.erb
# 変更前
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
 ↓↓↓
# 変更後
= form_with model: @user, url: session_path(resource_name), local: true do |f|
```

### ・　sign_up時のエラーが表示されない、after_sign_out_path_forが効かない
このエラーが出なくて、動作がおかしいことに気づきました。
コントローラーを書き換えることで治るようです。

app/controllers/turbo_devise_controller.rbに追記します。
```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
        render rendering_options.merge(formats: :html, status: :unprocessable_entity)
      else
        navigation_behavior error
      end
    end
  end

  self.responder = Responder
  respond_to :html, :turbo_stream
end
```
次に、config/initializers/devise.rbを開いて、TurboFailureAppクラスの定義を追加します。
```rb: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を変更します。
```rb:views/devise/registrations/new.html.erb
-  # config.parent_controller = 'DeviseController'
+  config.parent_controller = 'TurboDeviseController'

-  # 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 }にしたり、
アカウント削除のボタンはdata: { confrim: "Are you sure?" }ではなく、data: { turbo_confirm: "Are you sure?" }とします。


まとめ
---
Railsを学習して間もないため、HotwireもTurboもあまり分かっていなくなぜDevice gemと相性が良くないのかが分かっていませんでしたが、
記事を書くことで原因と解決法を整理して考えることができました。
今後もrailsのバージョンやgemのバージョンによってエラーが起きることも考えられるので、原因を１つ１つ追って整理して理解を深めたいです。