[rails7] turbo対応のdeviseの書き方

## この記事を読んで解決できる問題
* link_toのmethod　deleteが効かない問題
* Error: Form responses must redirect to another locationの問題
* deviseのUndefined method 'user_url'のエラー
* after_sign_out_path_forが効かない問題
* sign_up errorが表示できない問題

## この記事を書こうとしたきっかけ
rails7から色々大きく変わって、rails6など、以前のやり方だと、ほとんどの場合はerrorが起こります。
そしてrails7とdeviseについては、今の資料ではほとんどの場合はturbo自体を無効化にして使っているため、rails7の理念に合っていないので不採用です。正直できれば自前でログイン機能を実装するのが一番ですが、なんとしてもdeviseを使いたい場合もあると思います。皆様の参考になれればいいなと思います。

rails7からの変更点はこちらの方がうまくまとめてくれていますので、何が変わったのかを詳しく知りたい方はぜひ：
https://qiita.com/ryohashimoto/items/f5382478c78f296d8291

主の環境：
OS -> ubuntu20.04LTS
rbenv -> 1.1.1
ruby -> 3.1.2p20
rails -> 7.0.3.1 (use importmap)

importmap というのはrails7からdefaultになったimport手段です(pin構文)
詳しくは以下の記事をどうぞ：
https://zenn.dev/takeyuweb/articles/996adfac0d58fb
https://techracho.bpsinc.jp/hachi8833/2022_06_29/112183

---

### link_toのmethod　deleteが効かない
rails7からturboがデフォルトになったので、従来の```method: :post```などの書き方も合わせて
```data: { turbo_method: :post }```などに変更する必要がある
```ruby
# delete methodの例
link_to "表示させたい文字", path, data: { turbo_method: :delete }
# confirm機能を利用したい場合は以下のように書けます
link_to "表示させたい文字", path, data: { turbo_method: :delete, turbo_confirm: "ほんとに削除しますか?" }
```
:::note warn
turbo streamのdeleteメソッドを使う時は、必ずrenderやredirectにstatus: 303（see other）を返してください。返さないとstream自体が止まっていないため、他のデータも一緒に削除されます。非常に危険です。
:::
```ruby
# redirect_toを使う場合のstatusの返し方
redirect_to root_path, status: 303 # この2行の働きは全く一緒です
redirect_to root_path, status: :see_other # この2行の働きは全く一緒です
```

詳しく知りたい方はこちらのリンクへどうぞ:
https://github.com/hotwired/turbo-rails

---

### Error: Form responses must redirect to another location
上と同様、turboを使うと303statusが必須なので、statusで303を返してあげれば解決します。

---

### Undefined method 'user_url'のエラー
deviseではまだturboに対応したいないので、手動でこちらのファイルを変更する必要があります。
ファイル内でコメントアウトされているので、ctrl+fで検索していただければ探せると思います
```diff_ruby:config/initializers/devise.rb
- config.navigational_formats = ['*/*', :html]
+ config.navigational_formats = ['*/*', :html, :turbo_stream]
```
こちらのissueもご参照ください：
https://github.com/heartcombo/devise/issues/5439

---

### after_sign_out_path_forが効かない
* 原因
    * sign outはデフォルトではdelete methodを受け取ってsessionを終了しているため、turboになったrails7では、delete methodのredirect_toは必ずstatus 303を受け取らないといけない。しかしdeviseでは303statusを返してない。
* 解決法
    1. deleteを受け取ってsessionを終了するのをgetを受け取るように変更（非推奨）
        ```diff_ruby:config/initializers/devise.rb
        - config.sign_out_via = :delete
        + config.sign_out_via = :get
        ```
       変更した後のsign outはgetを受け取ってsign outするようになったので、元々のsign outのlink_toのdeleteを消します
        ```diff_ruby
        - link_to "Log out", destroy_user_session_path,  data: { turbo_method: :delete }
        + link_to "Log out", destroy_user_session_path
        ```
        しかしこうなると```csrfの脆弱性```の可能性がありますので```非推奨```です

    1. delete methodをオーバーライドして303statusを返すようにする
        オーバーライドするにはまずdeviseでカスタムcontrollerを指定する必要があります
        ```ruby:config/routes.rb
        devise_for :users, controllers: { sessions: "sessions" }
        ```
        これでdeviseのsessionsの機能だけデフォルトのcontrollerではなく、カスタマイズできる`app/controllers/sessions_controller.rb`を参照するようになりました。では実際にこのcontrollerを作って編集してオーバーライドしましょう
        ```ruby:app/controllers/sessions_controller.rb
        class SessionsController < Devise::SessionsController
            def respond_to_on_destroy
                respond_to do |format|
                    format.all { head :no_content }
                    format.any(*navigational_formats) { redirect_to after_sign_out_path_for(resource_name), status: 303 } # ここで303statusを返します
                end
            end
        end
        ```
        このコードでは、deviseのSessionControllerの中のrespond_to_on_destroyメソッドをオーバーライト（上書き）しています。元々303statusを返してないredirect_toに303を付けました。これでlink_toはdeleteのままturbo_methodを使えるようになりました

#### deviseのカスタムcontrollerについて
deviseでは普通、gem installされたデフォルトのcontrollerを参照しています。このcontrollerのファイルはgithub上の```app/controllers/devise```フォルダー内で見れます。
https://github.com/heartcombo/devise

deviseをinstallしたlocal環境でも同じコードが見れます。場所はgem install先のgemsフォルダーの中にあります。
gemのinstall先は```gem environment```コマンドで見れます。出力の中の```INSTALLATION DIRECTORY```に記載されています。
この```INSTALLATION DIRECTORY```に続いて```gems/devise-x.x.x/app/controllers/devise```の中に、github上と同じコードが入っている。

先のオーバーライドがやっていることとは、この中の```sessions_controller.rb```というファイルの中の```respond_to_on_destroy```を上書きしています。ctrl+fで検索すれば元コードが見つかるはずです。

deviseのカスタムcontrollerについて詳しく知りたい方こちらの記事をどうぞ：
https://toarurecipes.com/devise-customize/
https://github.com/heartcombo/devise#configuring-controllers

---

### sign_up errorが表示できない
上と同じく、sing upに関するメソッドをオーバーライドして、303 statusを返してあげれば解決できます。
```ruby:config/routes.rb
devise_for :users, controllers: { registrations: "registrations" } # 上はsessionsを指定したが、今回はregistrationsを指定する
devise_for :users, controllers: { sessions: "sessions", registrations: "registrations" } # 同時指定することもできます
```
これでdeviseが```app/controllers/registrations_controller.rb```を参照してくれるようになったので、実際にファイルを作ってコードを書きましょう
```ruby:app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
    def create
        build_resource(sign_up_params)
    
        resource.save
        yield resource if block_given?
        if resource.persisted?
            if resource.active_for_authentication?
                set_flash_message! :notice, :signed_up
                sign_up(resource_name, resource)
                respond_with resource, location: after_sign_up_path_for(resource)
            else
                set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
                expire_data_after_sign_in!
                respond_with resource, location: after_inactive_sign_up_path_for(resource)
            end
        else
            clean_up_passwords resource
            set_minimum_password_length
            respond_with resource, status: 303 # 今回は失敗するときのrespond_withにerror出したいので、ここで303 statusを追加
        end
    end
end
```
実際にどのメソッドをオーバーライドすれば良いかについては、状況に応じて、コードをじっくり読んで、deviseの挙動を知った上で判断いたしましょう。

---

以上です