---
title: Pitfalls in RESTful Wizards
teaser: Common problems when building wizard workflows in Rails.
tags: web,rails
author: Chad Pytel
published_on: 2008-04-25
---

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:

1. `teams/new`
2. `teams/:id/purchases/new`
3. `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:

```ruby
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:

```ruby
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](https://github.com/thoughtbot/when).

## Pitfall: 'not caching' only works in Firefox

To the best of our knowledge, setting the <abbr title="HyperText Transfer
Protocol">HTTP</abbr> headers should have worked in all browsers, but it did not
work in Safari or in IE 6 and 7. [A little more
research](https://developer.apple.com/internet/safari/faq.html#anchor5) 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:

```html
<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
[responds_to_parent](http://khamsouk.souvanlasy.com/2007/5/1/ajax-file-uploads-in-rails-using-attachment_fu-and-responds_to_parent)
?

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?

<figcaption>photo courtesy of [Michael Porter] via Flickr</figcaption>

[Michael Porter]: https://www.flickr.com/photos/libraryman/23075210/
