📸

【Rails】flash[:key]とflash.now[:key]の違いをざっくり説明してみた。【結論: 寿命が違う】

2021/05/30に公開

1.この記事の対象者

以下の方へ向けてこの記事を書いています。

  • Railsのflashを勉強したことがある方
  • 「flash勉強したけどよく分からん」と感じている方
  • flash[:key]とflash.now[:key]の違いを知りたい方

renderメソッドとredirect_toメソッドの違いを理解していない場合、この記事の内容が難しいと感じるかもしれません。その場合は以下の記事を一読することをオススメします。
https://zenn.dev/yukihaga/articles/fda5528484865c

結論を早く知りたい方は4章から読むことをオススメします。

2.この記事をなぜ書いたか

この記事を書いた理由は以下の2点です。

  • flash[:key]とflash.now[:key]の違いを説明している記事が少ないと感じた為。
  • 自分が勉強したことをアウトプットしたい為。

間違いがある場合、コメント頂けると嬉しいです。

3.そもそもflashとは何?

結論から言うと、flashはActionDispatch::Flash::FlashHashというクラスのオブジェクトです。
https://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html
言葉で説明しても分かりづらいので、ログインフォームを使って説明します。

  1. まず、事前にDBに登録してある情報を使ってログインします。各情報を入力後にログインボタンを押すと、画面が遷移してログインできます。ログイン後のページ上部にある緑色の通知内に書かれたメッセージが、flashオブジェクトに格納されているメッセージです。(UserSessionsコントローラのcreateアクション内でflashにメッセージを代入しています。今回の場合、keyはsuccessです。また、flashに格納したメッセージを画面上に表示するために、_flash_message.html.erbを作成しました。)
    Image from Gyazo
user_sessions_controller.rb
class UserSessionsController < ApplicationController
 
  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(posts_path, success: I18n.t('flash_message.login_success'))
    else
      flash.now[:danger] = I18n.t('flash_message.login_failure')
      render action: 'new'
    end
  end

end
_flash_message.html.erb
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
  1. 画面が遷移していく間で、どのような処理が行われているかをbinding.pryを使って確認します。このWebアプリケーションのPostsコントローラ内のindexアクションにbinding.pryを記述をします。(以下のコードでは、Postsコントローラ内の他のアクションを省略しています。)
posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]

  # GET /posts
  def index
    binding.pry
    @posts = Post.all
  end

end
  1. binding.pryをindexアクション内に書いたため、indexアクションの処理が途中でストップしています。そのため、ログインしようとすると画面が止まってしまいます。
    Image from Gyazo

  2. サーバー側の画面を確認すると、binding.pryを書いた箇所で処理が止まっていることが分かります。また、この画面から、rubyのコードを入力することができます。コマンドを入力することによって、パラメータの現在の状態を確認することができます。
    Image from Gyazo

  3. 試しにflashと入力してみます。すると、flashオブジェクトの現在の状態が出力されます。
    Image from Gyazo

  4. 終わり

以上から、flashのクラスが確認できたので、flashはオブジェクトであることが分かりました。(flash.object_idと打っていただくとflashのオブジェクトidも確認できます。)

4.本題

結論から言うと、flash[:key]とflash.now[:key]の違いは寿命です。flash[:key]の方が寿命が長いです。(寿命というのは、変数の寿命のことです。寿命が長いということは、長い時間変数に情報が保持されていることを意味します。)つまり、flash[:key]の方が長い時間メッセージが格納されているのです。

なぜそのような結論になったのかを説明したいので、以下の記事の引用文を見てください。
http://ionrails.com/2009/09/19/flashnotice-vs-flash-nownotice/

Flash[:notice]’s message will persist to the next action and should be used when redirecting to another action via the ‘redirect_to’ method.
Flash.now[:notice]’s message will be displayed in the view your are rendering via the ‘render’ method.

この記事を読んで分かったことは、flash[:notice]に代入されたメッセージは次のアクション終了時までflash[:notice]に残り続けるということです。(この記事ではkeyをnoticeとしています。)つまり、flash[:notice]はredirect_toメソッドを使って、別のアクションへリダイレクトしたい際に使うのがベストであることが分かります。また、flash.now[:notice]に代入されたメッセージはrenderメソッドを使ってレンダリングしたビュー上に表示されることが分かります。

ただし、上の記事だけだと、flash.now[:key]の寿命が本当に短いのかが分かりません。
そこで、以下の記事の引用文を見てください。
http://spaghettirefactory.blogspot.com/2015/03/rails-flash-vs-flashnow.html#:~:text=flash persists across an action,doesn't persist across requests

flash persists across an action, so if you use flash for a render, the flash message will show up on the rendered page AND the next page after clicking a link. Conversely, if you run flash.now on a redirect_to, you won't even see the message because the flash.now doesn't persist across requests.

この記事を読んで分かったことは、flash[:key]をもしrenderメソッドと一緒に使った場合、フラッシュメッセージはレンダリングされたページとクリックした次のページまで表示されるということです。つまり、flash[:key]にメッセージが保持し続けられることが分かります。(ページ内のリンクをクリックしたってことはサーバー側に特定のアクションに対応したHTTPリクエストを送っています。)また、もしflash.nowをredirect_toメソッドと一緒に使った場合、フラッシュメッセージは画面上に表示されないことも分かりました。(なぜなら、flash.nowは次のHTTPリクエストをまたいで、生存することができないからです。)

以上の2つの記事から、flash[:key]とflash.now[:key]の違いは寿命であり、flash[:key]の寿命の方が長いことが分かりました。

しかし、信頼性にかける記事である為、2つ目の記事の引用文を実装してみます。

5.実装

この記事の引用文を実装項目ごとに引用文1と引用文2に分けます。
http://spaghettirefactory.blogspot.com/2015/03/rails-flash-vs-flashnow.html#:~:text=flash persists across an action,doesn't persist across requests

flash persists across an action, so if you use flash for a render, the flash message will show up on the rendered page AND the next page after clicking a link. Conversely, if you run flash.now on a redirect_to, you won't even see the message because the flash.now doesn't persist across requests.


  • 引用文1

flash[:key]をもしrenderメソッドと一緒に使った場合、フラッシュメッセージはレンダリングされたページとクリックした次のページまで表示される

まず、上の引用文1を実装します。

  1. 先程のログインフォームのコントローラは以下のコードで作成されています。(newアクションやdestroyアクションは割愛してます。)
user_sessions_controller.rb
class UserSessionsController < ApplicationController

  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(posts_path, success: I18n.t('flash_message.login_success'))
    else
      flash.now[:danger] = I18n.t('flash_message.login_failure')
      render action: 'new'
    end
  end
  
end
  1. もし、ログインに失敗するとエラーメッセージが出ます。その後、ページ内のリンクをクリックしたり、ページを再読み込みするとエラーメッセージが消えることが分かります。
    Image from Gyazo

  2. user_sessions_controller.rb内のflash.now[:danger]をflash[:danger]に変更します。(今回は、flash.now[:key]とflash[:key]のkeyをdangerとしています。)

user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def new; end

  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(posts_path, success: I18n.t('flash_message.login_success'))
    else
      flash[:danger] = I18n.t('flash_message.login_failure')
      render action: 'new'
    end
  end

  def destroy
    logout
    redirect_to(login_path, success: I18n.t('flash_message.logout'))
  end
end
  1. コードを変更後、ログイン失敗してエラーメッセージを表示させます。その後、別のページへ遷移すると、なんとエラーメッセージがまだ残っています。その後、別のページへ遷移するとエラーメッセージは消えています。つまり、flash[:key]に代入されたメッセージは最初のアクション終了後と次のアクション終了後まで、生存していることが分かります。
    Image from Gyazo

  2. 引用文1は立証できたので終了です。

次に以下の引用文2を実装します。

  • 引用文2

もしflash.nowをredirect_toメソッドと一緒に使った場合、フラッシュメッセージは画面上に表示されない

  1. ログインするとフラッシュメッセージが画面上に表示されます。この画面から別の画面へ行くとフラッシュメッセージは消えます。
    Image from Gyazo
  2. 先程のコントローラのコードを一部変更します。redirect_back_toのオプションに、 success: I18n.t('flash_message.login_success')が指定されていますが、この記述を消します。その後、redirect_back_toが書かれた行の上にflash.now[:success]を追加します。変更後のコードを以下に記載します。(redirect_back_toメソッドはsorceryが提供するメソッドです。redirect_toメソッドと同じような挙動をします。)
user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def create
    @user = login(params[:email], params[:password])

    if @user
      flash.now[:success] = I18n.t('flash_message.login_success')
      redirect_back_or_to(posts_path)
    else
      flash[:danger] = I18n.t('flash_message.login_failure')
      render action: 'new'
    end
  end
end
  1. 実際にログインしてみます。ログインに成功しているのに、フラッシュメッセージが表示されないことが分かりました。つまり、flash.now[:key]に代入されたメッセージはアクション終了までしか生存できないことが分かります。(リダイレクト系のメソッドはHTTPリクエストをもう一度送れとHTTPレスポンスを通してブラウザ側に指示します。ブラウザ側が次のHTTPリクエストを送った瞬間、flash.now[:key]に代入されたメッセージは消えます。flash.now[:key]に代入されたメッセージは次のHTTPリクエストをまたいで生存することができないからです。)
    Image from Gyazo
  2. 引用文2を立証できたので終了です。もし、flash.now[:key]に代入されたメッセージがどのタイミングで消えたのかを知りたい方はbinding.pryを使って検証することがオススメです。

6.終わり

flash[:key]とflash.now[:key]の違いは分かりづらいです。この記事を通して一人でも多くの人の問題が解決されたら幸いです。

7.参考文献

https://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html
http://ionrails.com/2009/09/19/flashnotice-vs-flash-nownotice/
http://spaghettirefactory.blogspot.com/2015/03/rails-flash-vs-flashnow.html#:~:text=flash persists across an action,doesn't persist across requests
https://pikawaka.com/rails/flash

Discussion