この記事を読んで解決できる問題
- 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 }
などに変更する必要がある
# delete methodの例
link_to "表示させたい文字", path, data: { turbo_method: :delete }
# confirm機能を利用したい場合は以下のように書けます
link_to "表示させたい文字", path, data: { turbo_method: :delete, turbo_confirm: "ほんとに削除しますか?" }
turbo streamのdeleteメソッドを使う時は、必ずrenderやredirectにstatus: 303(see other)を返してください。返さないとstream自体が止まっていないため、他のデータも一緒に削除されます。非常に危険です。
# 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で検索していただければ探せると思います
- 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を返してない。
- 解決法
-
deleteを受け取ってsessionを終了するのをgetを受け取るように変更(非推奨)
config/initializers/devise.rb- config.sign_out_via = :delete + config.sign_out_via = :get
変更した後のsign outはgetを受け取ってsign outするようになったので、元々のsign outのlink_toのdeleteを消します
- link_to "Log out", destroy_user_session_path, data: { turbo_method: :delete } + link_to "Log out", destroy_user_session_path
しかしこうなると
csrfの脆弱性
の可能性がありますので非推奨
です -
delete methodをオーバーライドして303statusを返すようにする
オーバーライドするにはまずdeviseでカスタムcontrollerを指定する必要がありますconfig/routes.rbdevise_for :users, controllers: { sessions: "sessions" }
これでdeviseのsessionsの機能だけデフォルトのcontrollerではなく、カスタマイズできる
app/controllers/sessions_controller.rb
を参照するようになりました。では実際にこのcontrollerを作って編集してオーバーライドしましょうapp/controllers/sessions_controller.rbclass 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を返してあげれば解決できます。
devise_for :users, controllers: { registrations: "registrations" } # 上はsessionsを指定したが、今回はregistrationsを指定する
devise_for :users, controllers: { sessions: "sessions", registrations: "registrations" } # 同時指定することもできます
これでdeviseが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の挙動を知った上で判断いたしましょう。
以上です