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?
Comments
Cool ideas, thanks. I was actually unaware of the Rails helper methods for :notice and :alert, good to know.
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.
I’ve blogged about the same topic: http://thepugautomatic.com/2012/08/the-rails-flash/
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!
Spree uses it exactly the same way.
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.