Skip to content
Dave Strus edited this page Jul 15, 2015 · 1 revision

To form a message that users will see on the following page, we'll use a flash message.

From the Rails Guide for controllers:

The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for passing error messages etc.

It is accessed in much the same way as the session, as a hash (it's a FlashHash instance).

Each item in the FlashHash has a name (used as the key in the hash), and a message (the value). As a general rule, the name identifies what kind of message it is—for example, an error, notice, alert, etc.

We can assign messages to the flash directly:

flash[:error] = 'Whoops! Something went wrong. Please try again.'

Or we can assign messages via the options hash on a redirect:

redirect_to new_post_path, flash: { error: 'Whoops! Something went wrong. Please try again.' }

Let's use the second option in our controller.

app/controllers/posts_controller.rb

11  def create
12    post = Post.new(params.require(:post).permit(:title, :link, :body))
13    if post.save
14      redirect_to posts_path
15    else
16      redirect_to new_post_path, flash: { error: 'Whoops! Something went wrong. Please try again.' }
17    end
18  end

If we try submitting bad data again at this point, we won't see anything different. That's because we're not actually displaying flash messages anywhere. Let's put a binding.pry just before our main content in the layout and see how the flash messages appear in our views.

app/views/layouts/application.html.erb

      <main>
        <div class="well">
          <% binding.pry %>
          <!-- BEGIN main content -->
          <%= yield %>
          <!-- END main content -->
        </div>
      </main>

Now try submitting a long title again. Check out the server output, and you should have a pry prompt.

    16:   </div>
    17:   <div class="container">
    18:     <div class="row">
    19:       <div class="col-xs-8">
    20:         <main>
    21:           <div class="well">
 => 22:             <% binding.pry %>
    23:             <!-- BEGIN main content -->
    24:             <%= yield %>
    25:             <!-- END main content -->
    26:           </div>

[1] pry(#<#<Class:0x007fdb034f0070>>)>

Let's look for the flash.

[1] pry(#<#<Class:0x007fdb03e4b778>>)> flash
=> #<ActionDispatch::Flash::FlashHash:0x007fdb03448668
 @discard=#<Set: {"error"}>,
 @flashes={"error"=>"Whoops! Something went wrong. Please try again."},
 @now=nil>

As promised, flash is an object of type ActionDispatch::Flash::FlashHash. As its name suggests, it behaves like a hash. Let's have a look at the first item in the FlashHash.

[2] pry(#<#<Class:0x007fdb03e4b778>>)> flash.first
=> ["error", "Whoops! Something went wrong. Please try again."]

If we iterate over the FlashHash, each item in the hash will be an array containing the name of the flash message (such as "error") and the message itself.

A quick way to get this and other flash messages to display is to put each one in DIVs, using the name as a CSS class. We include the CSS class so that, down the road, we can style error messages differently than, say, notices.

Let's quit pry and update our layout. We'll remove binding.pry and replace it with any and all flash messages. We'll use the content_tag helper:

http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag

content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)

We'll pass the name of the tag as a symbol (:div), the content of the element (msg), and a CSS class for the name of the flash message, via the options hash ({ class: name }).

With ["error", "Whoops! Something went wrong. Please try again."] as the only item in flash, we expect to see content_tag return the following:

<div class="error">Whoops! Something went wrong. Please try again.</div>

Here's how it looks in our layout:

app/views/layouts/application.html.erb

      <main>
        <div class="well">
          <% flash.each do |name, msg| -%>
            <%= content_tag :div, msg, class: name %>
          <% end -%>
          <!-- BEGIN main content -->
          <%= yield %>
          <!-- END main content -->
        </div>
      </main>

If we try to submit a too-long title again, we'll finally see our error message. Trouble is, it doesn't actually tell us what the problem is—just that there is one. We could just change the hardcoded message to be more specific. After all, we're pretty sure it's because the title is too long this time, but that's far from the only reason post.save might fail.

Thankfully, when an ActiveRecord object fails to save, we can access the specific error message, which is quite readable, thanks to our validation.

[6] pry(#<PostsController>)> post.errors.full_messages
=> ["Title is too long (maximum is 255 characters)"]

Notice that message is contained in an array. We can pass that array as our flash message, and then iterate over it in the layout.

app/controllers/posts_controller.rb

11  def create
12    post = Post.new(params.require(:post).permit(:title, :link, :body))
13    if post.save
14      redirect_to posts_path
15    else
16      redirect_to new_post_path, flash: { error: post.errors.full_messages }
17    end
18  end

If we don't make any changes to the layout, the messages will render with the square brackets and quotation marks befitting an array. We'd rather show each message as a list item.

In the layout, we'll check if msg is an Array. If it is, we'll show each message in the array in a list.

app/views/layouts/application.html.erb

            <% flash.each do |name, msg| -%>
              <% if msg.is_a? Array -%>
                <div class="<%= name %>">
                  <ul>
                    <% msg.each do |message| -%>
                      <li><%= message %></li>
                    <% end -%>
                  </ul>
                </div>
              <% else -%>
                <%= content_tag :div, msg, class: name %>
              <% end -%>

That's not too bad. It would sure be nice not to have to start over with a blank form though, wouldn't it?

We can render the new form without actually redirecting to the page. If we assign our post to the instance variable @post, then the form should fill in with the previous values. Let's break it down line-by-line.

app/controllers/posts_controller.rb

15    else
16      flash.now[:error] = post.errors.full_messages
17      @post = post
16      render :new
18    end

On line 16, we use flash.now. Since we're no longer redirecting, we need to specify that the flash messages should be available to the current view, rather than the next one.

On line 17, we assign the instance variable @post. We want our form to use a post that already has data, rather than a blank post.

One line 16, we tell the controller to render a specific view template, rather than looking for a view with the same name as the controller action. In this case, it will look for app/views/posts/new.html.erb rather than the default app/views/posts/create.html.erb (which doesn't exist).

Let's try to submit an illegal title one more time.

Very nice. We have our error message, and the form is pre-filled with the values we previously submitted.

We've been submitting bogus posts for so long, I forget what it looks like to actually save one successfully. Let's try that again.

OK, it sends us back to the index. Our new link shows up in the list, if we hunt for it, but there's no other indication that our previous action succeeded.

You know what? It would be nice to see a flash message after a successful save too.

app/controllers/posts_controller.rb

12    post = Post.new(params.require(:post).permit(:title, :link, :body))
13    if post.save
14      redirect_to posts_path, flash: { notice: 'Your post was saved successfully.' }
15    else

Be sure to check out the new message in your browser.

If you like what you see, let's commit our changes. Don't forget to check your git status, and maybe even git diff, to make sure you're committing what you expect.

$ git add .
$ git commit -m "Display flash messages upon saving (or failing to save) posts."