Non-Message Flash in Rails

Flash messages were one of those little features that amazed me when I was first introduced to Rails. Developers often use Rails' flash to display messages to their users, but messages aren't the only reason to use flash.

Message Flash

A typical interaction with flash looks something like this in the controller:

class OrdersController < ApplicationController
  def new
    @order = Order.new
  end

  def create
    @order = Order.new(params[:order])

    if @order.save
      redirect_to confirmation_path, notice: "Thank you!"
    else
      render :new
    end
  end
end

and in the view:

<% if flash.any? %>
  
    <% flash.each do |key, message| %>
      <%= message %>
    <% end %>
  
<% end %>

This is a common pattern. However, you can see that in the view, we're making the (unsafe) assumption that every value in the flash is intended to be displayed as a message to the user. This assumption prevents you from using flash in any other way.

Message Whitelist

Before we can use flash for something other than displaying messages, we have to whitelist which keys we intend to use for messaging.

You can choose any keys you'd like, but it's worth mentioning that Rails has a built-in preference for the :alert and :notice keys. The flash object itself has special setter and getter methods for these two keys and the redirect\_to controller method accepts options specifically for setting these two values.

Using the example above, our view might change to this:

<% if message_flash.any? %>
  
    <% message_flash.each do |key, message| %>
      <%= message %>
    <% end %>
  
<% end %>

We're using the message\_flash helper in the view, which we define ourselves:

module ApplicationHelper
  def message_flash
    flash.to_hash.slice("alert", "notice") # to_hash casts keys to strings
  end
end

Non-Message Flash

Now we're free to use any other flash key for non-message purposes. For example, we can use flash for transactional analytics.

Google Analytics is great for tracking pageviews but it also has the ability to track ecommerce data. Let's assume we're already tracking pageviews via Google Analytics with the standard snippet in our view:

  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  ga('create', '<%= ENV["GOOGLE_ANALYTICS_KEY"] %>', 'auto');
  ga('send', 'pageview');

We can start to track transactions by changing the snippet to:

  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  ga('create', '<%= ENV["GOOGLE_ANALYTICS_KEY"] %>', 'auto');
  <% if flash[:order] %>
    ga('require', 'ec');
    ga('ec:setAction', 'purchase', <%= raw(flash[:order].to_json) %>)
  <% end %>
  ga('send', 'pageview');

In our controller, we need to add pertinent transaction information to the flash:

class OrdersController < ApplicationController
  def new
    @order = Order.new
  end

  def create
    @order = Order.new(params[:order])

    if @order.save
      flash[:order] = {
        id:       @order.id,
        revenue:  @order.total,
        shipping: @order.shipping,
        tax:      @order.tax
      }

      redirect_to confirmation_path, notice: "Thank you!"
    else
      render :new
    end
  end
end

Now, when a user places an order, Google Analytics will track the transaction. Using Rails' flash is helpful because if the user refreshes the order confirmation page, flash\[:order\] is dropped and the transaction won't be tracked twice.

In what other scenarios could non-message flash be useful?

steve@collectiveidea.com

Comments

  1. January 29, 2015 at 5:28 AM

    Cool ideas, thanks. I was actually unaware of the Rails helper methods for :notice and :alert, good to know.

  2. January 29, 2015 at 14:06 PM

    I like the abstraction for display the message based flash messages.

    I don’t care for the other use case though, it seems very weird to me that you would want to capture metrics about your site via google analytics populated with data from the flash. Wouldn’t it make more sense to set up a metric event store separate from google analytics like statsd or new relic insights and capture the metrics before redirecting to the new page?

    Maybe there are better use cases, but it seems like using flash for things that are not messages is not how they were intended to be used.

  3. January 29, 2015 at 15:29 PM

    I’ve blogged about the same topic: http://thepugautomatic.com/2012/08/the-rails-flash/

  4. January 29, 2015 at 19:06 PM

    I like the idea, though I would give it bonus points if you actually used a separate object that behaved similarly to flash rather than piggy-backing off of flash. But hey, this works in a pinch and is a neat repurposing of the general mechanism. Good on ya!

  5. coorasse@gmail.com
    Alessandro
    January 30, 2015 at 3:35 AM

    Spree uses it exactly the same way.

  6. February 08, 2015 at 11:47 AM

    Nice post. I’m used to use background jobs to manage these analytics data, mainly because in my case the data is sent when the order is paid.
    But this approach can be used to solve other problems, like sending transactional emails and avoid sending the same email twice when the user refreshes the same page.