In an application we’re currently building, users go through a wizard to order teams. We implemented this as RESTful controllers - teams, purchases, and orders.
The relevant wizard steps are:
teams/new
teams/:id/purchases/new
orders/new
When each step is submitted, the related create action is called and the user is redirected to the next new action.
Pitfall: the user hits the back button
On step 3, the user hits the back button. If they re-submit step 2, they will get a validation error because they cannot create two purchases for a team.
There’s a temptation here to stray from RESTful design. Don’t!
When the user hits the back button from step 3, we want to send them to purchases/edit for their newly-created object instead of purchases/new.
Solution: Force the browser to not cache
To ensure that the page isn’t cached by the browser and will always be re-fetched, we add a before_filter to the purchases controller which calls a private method on ApplicationController:
before_filter :no_cache, :only => [:new]
private
def no_cache
response.headers["Last-Modified"] = Time.now.httpdate
response.headers["Expires"] = 0
# HTTP 1.0
response.headers["Pragma"] = "no-cache"
# HTTP 1.1 'pre-check=0, post-check=0' (IE specific)
response.headers["Cache-Control"] = 'no-store, no-cache, must-revalidate,
max-age=0, pre-check=0, post-check=0'
end
Next, if the team already has a purchase, we redirect to the edit action using another before filter on the purchases controller:
before_filter :redirect_to_edit, :only => [:new], :if => :team_has_registration_purchase?
#team_has_registration_purchase?
is a method on the purchases controller. If
the :if
syntax is unfamiliar to you, its provided to us by our plugin,
when.
Pitfall: ‘not caching’ only works in Firefox
To the best of our knowledge, setting the HTTP headers should have worked in all browsers, but it did not work in Safari or in IE 6 and 7. A little more research proved that any page with an iframe on it will never be cached, and will always be refetched.
Solution: iframe
So, we add this to views/purchases/new:
<iframe style="height:0px;width:0px;visibility:hidden" src="about:blank">
this frame prevents back forward cache
</iframe>
This works. We now have cross-browser no-caching in a RESTful wizard.
The iframe hack feels a little dirty, though. Does anyone know a better way? Or, do we live with it, similar to using a hidden frame for Ajax file uploads with respondstoparent ?
This concept might be useful outside of these more complex, wizard type controller actions, and might come in handy if you had just a normal restful controller where you wanted the user to get the edit action instead of the new action if they use the back button. Have you done something like this before, or can you think of a different way to accomplish that?