A Word About Flash Messages

Beginner

When you first learn Rails, you will learn about an important little tool call flash messages. These messages are shown to your user to alert them of important information. Rails will stuff the information from your flash messages into its session, and pull it out again on the next request. There are some tricks you can use to massage your flash message to how you want them to appear.

In a controller, for example, you might find yourself writing respond_to. Inside your respond_to block you might set a flash when something is either a success or failure. (Here, I’ve only shown using a flash on failure.)

Let’s say you go to update an account:

respond_to do |format|
  if @account.save
    # do nothing here
  else
    flash[:alert] = "Oops, your account could not be saved."
  end
  format.html
end

In our example, the @account object might reject the save if there are invalid fields, returning false, and thus you would set a flash[:alert] message. (You should set flash[:alert] when something has gone wrong and flash[:notice] when something has gone right or is neutral.)

Example 1 — https://github.com/the-rails-coach/flash-test1

Let’s take a second academic example thing-test1. In this example, we have an object Thing. Our Thing object contains an age, which must be a number and must be greater than 18, and a name, which must not be empty.

When I go to edit the object, it looks like so:

Age Name

Our Rails code that generates this is simple enough:

app/views/edit.html.erb

Please update your thing
<%= form_for @thing do |f| %>
<%= f.text_field :age %>
Age
 <br/>
<%= f.text_field :name %>
Name
 <br/>
<%= f.submit "Save" %>
<% end %>

You’ll note that we show our flash messages in the application.html.erb file, like so:

<!DOCTYPE html>
<html>
  <head>
    <title>FlashTest</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>

    <%= render partial: 'layouts/flash_notices' %>
    <%= yield %>
  </body>
</html>

And finally, the partial in layouts/_flash_notices.html.erb looks like this:

<% unless notice.nil? %>
  <div class="alert alert-notice alert-dismissible">
    <%= notice %>
  </div>
<% end %>
<% unless alert.nil? %>
  <div class="alert alert-danger alert-dismissible">
    <%= alert %>
  </div>
<% end %>

I’ve loaded up Bootstrap here also so that the alerts look nice.

When I give the interface an invalid age (17) and no name (empty), I see a flash notice telling me the input is invalid:

With Error Message

How does this work? Well, let’s take a look at the controller:

class ThingsController < ApplicationController
  def edit
    @thing = Thing.find(params[:id])
  end
  
  def update
    @thing = Thing.find(params[:id])
    @thing.update(thing_params)
    if @thing.save
      respond_to do |format|
        format.html
      end
    else
      flash[:alert] = "Oops, your thing could not be saved: " + @thing.errors.collect{|k,v| "#{k} #{v}"}.join(", ")
      redirect_to edit_thing_path(@thing)
    end
  end
  
  def thing_params
    params.require(:thing).permit(:name, :age)
  end
end

Push-In-Pull-Out

This works because the flash object is a push-in-pull-out object. That is, after you put the message into it the message it will persist until you take the message out of it. In this example, we’re using a ‘standard’ HTML response for edit & update. For the update, however, when the action is a failure the controller redirects instead of responding directly with content.

The redirect causes a second request to be sent to the server. For this reason, we stuff the flash message into the session (into the flash object). This happens just before sending back the redirect response. On the user’s next request, we’ll pull it out of the object to display it. This is why you see it in red at the top of the screen.

That works fine in the context of html rendering because the view gets rendered after the controller action. This “flushes” the flash message.

Sometimes, however, you see some funky Rails side-effects. For example, the flash message gets stored inside of Rails and doesn’t show up for the user until the next time the user loads a page.

That happens whenever you request an Ajax page (JS request). This happens even if the response uses the flash message via the Javascript response.

Example 2 — https://github.com/the-rails-coach/flash-test2

Consider the second example flash-test2.

In this example, I’ve refactored the responses to use Ajax, and so our forms now have remote: true

%div.edit-thing
  Please update your thing

  = form_for @thing, remote: true do |f|
    = f.text_field :age
    Age
    %br
    = f.text_field :name
    Name
    %br
    = f.submit "Save"

In the controller, the edit response now does a funny trick to render a partial as-if it is a full response.

class ThingsController < ApplicationController
  def show
    @thing = Thing.find(params[:id])
    render 'things/_edit'
  end

  def edit
    @thing = Thing.find(params[:id])
    render 'things/_edit'
  end

  def update
    @thing = Thing.find(params[:id])
    @thing.update(thing_params)
    respond_to do |format|
      if @thing.save
      else
        flash[:alert] = "Oops, your thing could not be saved: " + @thing.errors.collect{|k,v| "#{k} #{v}"}.join(", ")
      end
      format.js
    end
  end

  def thing_params
    params.require(:thing).permit(:name, :age)
  end
end




Notice that the update action always responds with Javascript, but the edit and show still respond with HTML.

The update.js.erb looks like this:

<% if @thing.errors.any? %>
$(".edit-thing").html("<%= j render partial: 'edit' %>")
$(".flash-notices").html("<%= j render 'layouts/flash_notices' %>");
<% else %>
$(".edit-thing").html("<%= j render partial: "things/show" %>")
<% end %>

Notice that in the update Javascript, I check for errors and, if there are any, render the flash_notices partial into the layout where the flash notices div is (replacing it with the new content).

Now, an invalid input does exactly what it did before: it tells you the invalid fields. Because we have removed the redirect, Rails now will cache the message and display it to the user twice.

If, for example, you reload your browser window or navigate again to the same URL (or navigate to another URL in the same app), Rails will hang onto that message even though it already displayed it. Your Rails app displays the message a second, redundant time. This is called the “double flash effect” and is seen often in Rails apps that mix JS responses, redirect responses, and HTML responses.

Your Rails app displays the flash message a second, redundant time.

To fix this problem, add this to your ApplicationController.

after_action -> { flash.discard }, if: -> { request.xhr? }

This will discard the messages after all Ajax (XHZ) requests, thus preventing them from being shown again to the user.

I hope you’ve enjoyed this tour of flash messages. Flash message nuances are some of the things you will encounter as a Rails developer. Be sure to like or follow the social media platform of your choice (see below) for more!