ちょっと困ったこと
Railsでよくあるflashメッセージを出そうと頑張っていたが、flash.nowだけ出せない。。。
例えば、以下のようにSessionControllerで、ユーザログインを行うようなコードを書く。
def create
@user = User.find_by(login_id: params[:session][:login_id])
if @user && @user.authenticate(params[:session][:password])
log_in(@user)
redirect_to @user, success: 'ログインしました'
else
flash.now[:danger] = 'ログインに失敗しました。'
render :new
end
end
ログイン成功したとき、すなわち、redirect_toした後の「ログインしました」は出るんだけど、ログイン失敗した時、すなわち「ログインに失敗しました」が全く表示されないという問題が発生しました。
問題点
結論から言うと、form_withに対してlocal: trueをつけていなかったからでした。
そのため、以下のようにlocal: trueをつけたらうまくいきました。
= form_with scope: :session, url: login_path, local: true do |f|
= f.label :login_id, 'ログインID'
= f.text_field :login_id, { class: 'form-control' }
= f.label :password, 'パスワード'
= f.password_field :password, { class: 'form-control' }
= f.submit 'ログイン', class: 'btn btn-primary'
なぜlocal: trueが必要か
話がそれますが、form_withにはlocalオプションなるものがあることは知っていました。form_withを使うにあたって、色々調べながら実装していったわけで、参考にする情報は大体local: trueがついていました。
ただ、別にlocal: trueなくても、普通にログインセッション張れるし、つけなくてもいいんじゃね?ということに気がついたので、local: trueは外していました。しかしながら、今回こういう壁にぶち当たった以上、このlocal: trueって何ですか、というところを放置するのはよくないと思ったので、もう少し深堀してみます。
そもそもform_withにおけるlocalオプションって何?
とりあえずRailsドキュメントを見ましょう。https://railsdoc.com/page/form_with
すると「local:リモート送信の無効」と書いています。これだけでは何のことだかさっぱりです。
もう少し詳しく見たかったので、こちらをあたりました。https://techracho.bpsinc.jp/hachi8833/2020_03_09/39502#6
すると「local: trueを指定するとフォームのリモート + unobtrusive XHR送信が無効になります(デフォルトのフォームではリモート + unobtrusive XHRが有効になります)」という記載があります。
これだけ見るとはてなマークばかりですが「unobtrusive XHR送信」というのはAjax通信のことです。というわけで、form_withにおけるlocalオプションは「当該箇所のform_withはAjax通信を無効(true)にするか、有効(false)にするか」ということになります。
Ajax通信と今回のflashメッセージ問題考察
さて、ここからは完全に憶測になるので、誤った記載となると思います。参考にする場合はほどほどでお願いします。ちなみに、Ajaxについての詳しい記載は本筋とは少し違うので、省略します。詳細を知りたい方は、本記事最後の参考をどうぞ。
考察
元のコードでうまくいかなかった理由
- form_withでlocalを指定しない場合、デフォルトでlocal: falseが設定される、すなわち、Ajax通信有効となっている。
- 今回私が書いたコードもそうなっている。
- これにより、ログインセッションを作成する処理は非同期通信で行われている。
- 非同期通信のため、クライアント側からサーバ側にログインセッションを生成しに行ってもそれがうまくいったかどうかはサーバ側から、クライアント側に返ってこない
- 例えば、HTTPステータス200とか404とかの情報が返ってこない。
- 今回のケースで言うと「ログイン失敗した」という情報がサーバ側から返ってこないため「flash.now[:danger]」は実行されない。
ところで冒頭に書きましたが「local: trueがない状態で、サーバ側からログインセッション生成しに行った結果が返ってこないのに、なぜ「ログインしました」のメッセージが出て、ログイン後の画面に遷移するのか」という次の疑問が出てきます。
これは、renderメソッドとredirect_toメソッドの違い、というところにあるかと思っています。ざっくりいうと、renderはHTTPリクエストをサーバに送らず、redirect_toはHTTPリクエストをサーバに送ります、というところですね。
今回のケースではログイン処理後にredirect_toを実行している、すなわちサーバからリクエストを送っています。redirect_toメソッドはリクエストに応じたHTMLをクライアントに返すので、クライアント側で画面遷移が発生します。
local: trueでうまくいった理由
上記の裏返しなんですが、local: trueにして、Ajax通信を無効、すなわち同期通信をすることによって、サーバからログインできたかどうかの結果がちゃんと返ってきます。それを検知して、flashメッセージが出せるようになるわけです。
参考記事
-
MDN
https://developer.mozilla.org/ja/docs/Web/Guide/AJAX/Getting_Started -
MDNよりわかりやすいQiitaの記事
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9 -
renderとredirect_to
https://qiita.com/january108/items/54143581ab1f03deefa1