Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Cacheable Flash

Pivotal Labs
Wednesday, August 8, 2007

Page caching is an easy way to get massive performance and scalability increases with little up front effort.
Of course, when page caching, a number of design changes are necessary. For example, server side session data cannot be used to render data on cached pages.

Rails provides the flash hash to easily render alert messages and errors. In the controller you can write to the flash hash:

flash[:error] = "You cannot go there"
# or
flash[:notice] = "Welcome to eternity"

and render the flash hash in the view. Typically the rendering happens on a layout:

<div id="error_div_id" class="flash flash_error"><%= flash[:error] %></div>
<div id="notice_div_id" class="flash flash_notice"><%= flash[:notice] %></div>

There are some strange quirks with using flash such as needing to use FlashHash#now when rendering the response without redirecting.

Everything works great until you need to page cache the landing page.

For example, lets say you page cache your home page. After logging in, you are redirected to the home page with the flash notice “Logged in successfully”. When page caching, this solution does not work because the request is responded to by the Web Server (i.e. Apache) and does not reach the Rails App server (i.e. Mongrel).

This means the view does not get a chance to render the flash error and notice.

There are a couple of solutions to this problem.

  • Do an AJAX request back to the server to render the flash error and notice (i.e. using RJS)
  • Send the flash error and notice from the server with cookies and render it during page load

Introducing Cacheable Flash

To solve the problem using cookies on Peer to Patent, we wrote the Cacheable Flash plugin. The plugin allows you to set the flash hash as normal on the controller. It handles converting the flash hash into cookies. All you need to do is include the CacheableFlash module into your controller.

class ApplicationController < ActionController::Base
  include CacheableFlash
  # ...
end

On the view side, you will need to add some javascript.

<div id="error_div_id" class="flash flash_error"></div>
<div id="notice_div_id" class="flash flash_notice"></div>
<script type="text/javascript">
  Flash.transferFromCookies();
  Flash.writeDataTo('error', $('error_div_id'));
  Flash.writeDataTo('notice', $('notice_div_id'));
</script>

The Flash.transferFromCookies method:

  • Grabs the flash data from the cookies and saves it to Flash.data
  • Deserializes the flash hash from JSON to Javascript
  • Erases the flash data from the cookies

The Flash.writeDataTo method:

  • Writes data from the passed in key in Flash.data to the passed in element or element id

Here is how the life cycle of the login works:

  1. The user enters the correct login information
  2. Rails handles the web request. In the Login controller, flash[:notice] is written to

      if current_user
        flash[:notice] = "Welcome to Eternity"
      end
    
  3. An after filter serializes contents of the Flash Hash as JSON into cookies

  4. The after filter clears the flash hash
  5. The cached page is rendered
  6. The client side receives and clears the flash cookie data
  7. The client side javascript renders the flash messages

There is also a side benefit — you don’t have to use FlashHash#now because storing the flash in cookies to be rendered and erased by the client makes this unnecessary.

The Cacheable Flash plugin is on the Pivotal RB project page (http://rubyforge.org/projects/pivotalrb). You can install it by running:

ruby script/plugin install svn://rubyforge.org/var/svn/pivotalrb/cacheable_flash/trunk

It will copy flash.js, cookie.js, and json.js if you do not already have these files.

Happy Page Caching!!

Thanks to Josh Susser for pairing with me on this.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

33 Comments

  1. pt says:

    so sweet.

    December 12, 2007 at 11:50 pm

  2. pt says:

    so sweet.

    December 12, 2007 at 11:50 pm

  3. szeryf says:

    bookmark added :)

    December 12, 2007 at 11:50 pm

  4. mikeymckay@gmail.com says:

    So dumb question – but how do I do the caching part? I have some reports that take forever to run. I want to cache them overnight. At the moment I am doing it via a sloppy hack. But this post implies that rails has a builtin way?

    December 12, 2007 at 11:50 pm

  5. choonkeat says:

    thanks for getting to it! this community is great :-)

    December 12, 2007 at 11:50 pm

  6. Ismael says:

    Is’t it possible to overload flash[:something].to_s so it writes the needed Javascript for you? That way you would render the flash as usual.

    December 12, 2007 at 11:50 pm

  7. Brian Takita says:

    Ismael, that is an interesting idea.

    The after filter would need to be changed to generate the javascript. The client side can then eval the contents of the cookie.

    We can remove the 2 Flash.writeDataTo calls by using generating the JS using the key/value pairs in the Hash.

    Nick recommended we use convention for the element ids.
    For example, the error and notice elements would have the ids “flasherror” and “flashnotice”.

    I’d like to have the after filter generate the JS by default, and still have a hook to easily override this behavior.

    December 12, 2007 at 11:50 pm

  8. Brian Takita says:

    Mikey, you can use Rails standard page caching and have a cron expire the page, by deleting the page, every night.

    You can also use a cron strategy where your cron does a HTTP request and writes the output to the cached file.

    December 12, 2007 at 11:50 pm

  9. ml says:

    Hm, how does fallback for non-javascript browsers work?

    December 12, 2007 at 11:50 pm

  10. John says:

    Have you guys thought about modifying this so that you can replace any portion of the page, not just the flash, with a dynamic bit and still cache the whole page? I’m thinking of your login example, showing the logged in users name on the page somewhere as well as the flash.

    December 12, 2007 at 11:50 pm

  11. Brian Takita says:

    ml, there is no fallback built into the plugin.

    You could make your own fallback by extracting the JS rendering into a helper method. There can be a state that will either render using javascript or render directly on the server side.

    If you have a non-js browser, it would be difficult (I’m not saying impossible) to render dynamic content using page caching.

    December 12, 2007 at 11:50 pm

  12. Brian Takita says:

    John, on Peer to Patent and other projects at Pivotal, we do that by having a javascript conditional in the page.

    For example, there is the header that shows “Hello, John” when you are logged in and the Login links when you are not logged in. We need to render the html for both scenarios to page cache this.

    We a javascript conditional that looks into the cookies to see if you are currently logged in, to decide which html to show.

    This can definitely be the topic of another blog post.

    You can see more about Peer to Patent’s caching strategy at Rails, Slashdotted: no problem.

    December 12, 2007 at 11:50 pm

  13. Don Parish says:

    On Rails 1.2.3, I had to include the json gem in the init.rb of the plugin for it to work for me.

    December 12, 2007 at 11:50 pm

  14. Brian Takita says:

    Don, Thank you for the bug report. I fixed it.

    December 12, 2007 at 11:50 pm

  15. thomas says:

    I have used similar strategies to page cache pages and they have worked well.
    On places like the homepage I usually store the user info in a cookie and then construct the html needed on page load

    December 12, 2007 at 11:50 pm

  16. Chris Saylor says:

    Great plugin! I styled my flash divs so I had to hack it a bit. Set a style display: none on the divs. Then inside flash.js move the line

    element.innerHTML = unescape(content);

    inside the if(Flash.data[name]) statement.

    Now add after that line, still inside the if statment, the follow effect

    element.toggle(‘appear’);

    Now my styled flashes only show up when there is content.

    December 12, 2007 at 11:50 pm

  17. weepy says:

    Actually I’ve just written a plugin (called CacheBack) that has similar functionality, but is more generic – so you can cache not just the flash, but other aspects e.g. login details in the nav or show non-sensitive conexts to each logged in user.

    If anyones interested – i can make it public.

    *…(

    December 12, 2007 at 11:50 pm

  18. jasiek says:

    How do I test this thing? It looks like after I do a get :index or whatever, the flash dictionary is emptied.

    December 12, 2007 at 11:50 pm

  19. Doug says:

    @weepy – I’d definitely be interested…let me know if you open it up, dougcole at gmail

    December 12, 2007 at 11:50 pm

  20. Brian Takita says:

    weepy – Please post the url for your plugin. I couldn’t find it.

    December 12, 2007 at 11:50 pm

  21. Brian Takita says:

    jasiek – I will add test helpers to the plugin soon.
    In the mean time, add this to your test or spec helper.

      def flash_cookie
        return {} unless cookies['flash']
        JSON.parse(cookies['flash'].first)
      end

    December 12, 2007 at 11:50 pm

  22. Tom says:

    I’m using the cacheable flash, and loving it, except for one thing:

    Firefox 2.0.0.8 (maybe earlier versions too) and IE 6 seem reluctant to erase the cookie on the client side, so when I refresh a page, or navigate around, my flash information gets displayed for a few pages rather than just one.

    I’ve tried changing the code that erases the cookie to modify the behavior, but with no success

    Is anyone else seeing this?

    December 12, 2007 at 11:50 pm

  23. Tom says:

    I’m using the cacheable flash, and loving it, except for one thing:

    Firefox 2.0.0.8 (maybe earlier versions too) and IE 6 seem reluctant to erase the cookie on the client side, so when I refresh a page, or navigate around, my flash information gets displayed for a few pages rather than just one.

    I’ve tried changing the code that erases the cookie to modify the behavior, but with no success

    Is anyone else seeing this?

    December 12, 2007 at 11:50 pm

  24. Henrik N says:

    I ran into an issue on edge Rails where one of my before filters set a flash and redirected: this would prevent the Cacheable Flash after_filter from running at all.

    My fix was simply to make Cacheable Flash use an around_filter instead (doing nothing before the yield) since these aren’t interrupted.

    December 12, 2007 at 11:50 pm

  25. Brian Takita says:

    Henrik, thanks for the report. I’ll add the fix.

    December 12, 2007 at 11:50 pm

  26. Brian Takita says:

    Tom, are you still having the issue?
    Are you doing custom things with your Rails cookies, such as changing the path?

    December 12, 2007 at 11:50 pm

  27. msq says:

    Thanks! Very useful plugin.

    December 12, 2007 at 11:50 pm

  28. Jonathan C says:

    Works like a charm, thanks so much!

    December 14, 2007 at 1:00 am

  29. Bala Paranj says:

    Brian,

    In my application.html.erb layout I have:

    flash.each do |key,value|
    div id=”flash” class=”flash_key
    span class=”message” value span
    div
    end

    Should I use the flash as the value for the div id? Will it work?

    May 21, 2008 at 1:17 am

  30. Brian Takita says:

    @Bala – Do you mean using the keys in your flash hash as the div id?
    There’s no reason why it wouldn’t work. The thing you may need to be concerned about is defining a flash key of an already existing id on your page.

    June 20, 2008 at 5:57 pm

  31. Michael Erb says:

    I added:

    ActionView::Helpers::AssetTagHelper.register_javascript_include_default(‘cookie’,'flash’,'json’)

    to cacheable_flash.rb at line 2 so I didn’t have to change my layouts.

    Yes, I’m that lazy :o)

    June 23, 2008 at 3:38 am

  32. Brian Takita says:

    @Michael – Thank you. I added your changes.

    Also, I added cacheable-flash to github.

    [http://github.com/pivotal/cacheable-flash](http://github.com/pivotal/cacheable-flash)

    June 25, 2008 at 8:02 am

  33. Lee Fyock says:

    We’re seeing the same issue (I believe) as Tom with IE6 — the flash hangs around for a few page loads after it should have gone away.

    Also, it’s fairly easy to get the flash to concatenate messages on successive page loads, rather than clearing the flash between page loads. Just post form data a couple of times before the entire page has time to load.

    So, on our login page, if you just hit return without putting in a name and password, you see “Invalid user/password combination”. Hit return again, and the flash now says “Invalid user/password combinationu003Cbr/u003EInvalid user/password combination”. Keep hitting return, and they keep concatenating. If you allow the page to completely load, then you’re OK.

    This is reproducible in FF3 for Mac, Safari for Mac, and IE7, at least.

    Does anyone have any ideas on how to prevent this?

    Does anyone have any ideas on the IE6 issue?

    Thanks!
    Lee

    January 14, 2009 at 8:02 pm

Add New Comment Cancel reply

Your email address will not be published.

Pivotal Labs

Pivotal Labs

Recent Posts

  • Does the set of all sets contain itself?
  • Standup 3/8/2012
  • Standup 3/7/2012
Subscribe to Pivotal's Feed

Author Topics

riddles (1)
agile (167)
capistrano (2)
rails (26)
movember (1)
git (10)
railsdoc (1)
object-design (1)
bdd (3)
cucumber (3)
linkedin (1)
oauth (1)
ruby (17)
tdd (2)
lvh.me (1)
rails 3.1.1 (1)
selenium (6)
homebrew (1)
mysql (5)
rvm (1)
sproutcore (1)
paperclip (2)
pry (1)
amazon (1)
heroku (1)
rails3 (2)
jasmine (3)
design (3)
process (12)
productivity (8)
learning (1)
olin (1)
migrations (2)
mongodb (2)
devise (2)
javascript (13)
rubymine (4)
ipad (1)
whurl (1)
head.js (1)
pairing (2)
tools (4)
pair programming (1)
rspec (10)
rspec2 (1)
ruby19 (1)
incubation (3)
startup (5)
api (1)
presenter (1)
vanna (1)
pivotal tracker (5)
capybara (1)
fakeweb (1)
webmock (1)
intern (1)
ruby on rails (25)
meetup (1)
textmate (1)
testing (20)
solr (4)
nyc-standup (11)
community (1)
opensource (3)
activerecord (4)
chrome (1)
mp4 (1)
activeresource (1)
flash (3)
neo4j (1)
nginx (1)
rsoc (1)
meta programming (1)
agile standup (7)
government (3)
webos (4)
xss (1)
jquery (1)
bundler (2)
ci (3)
gems (5)
postgresql (1)
geminstaller (1)
gemcutter (1)
cloud (2)
rack (2)
refraction (1)
gem (5)
refactoring (1)
validations (1)
webrat (1)
engine-yard (1)
firefox (2)
jsunit (1)
mongrel (2)
thin (1)
unicorn (1)
facebook (1)
rubygems (5)
jruby (1)
actioncontroller (1)
rails 2.3 (1)
palmpre (1)
autotest (1)
mac (2)
hosting (1)
goruco (11)
database (3)
railsconf (11)
gogaruco (4)
deployment (4)
github (1)
ie (1)
ajax (1)
intellij (1)
json (1)
asset packaging (1)
polonium (1)
character encoding (1)
utf-8 (1)
test (3)
civics (1)
hpricot (1)
rake (3)
sms (1)
unicode (1)
iphone (1)
java (1)
safari (1)
memory leaks (1)
rr (3)
editor (1)
css (1)
nyc (3)
performance (5)
fun (5)
enterprise rails (1)
health (1)
new and cool (1)
general (2)
treetop (1)
errors (1)
stack (1)
trace (1)
cache (1)
cookies (1)
freesoftware (1)
conferences (1)
development (1)
driven (1)
proxy (1)
caching (1)
peertopatent (1)
languages (1)
rest (2)
rubyforge (1)
sake (1)
file (1)
upload (1)
constants (1)
osx (1)
terminal (1)
pairprogramming (2)
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >