---
title: it's the wiz and nooobody beats it
teaser: Wizard workflows in web apps.
tags: web,rails
author: Jared Carroll
published_on: 2007-08-06
---

We all love wizards.

Here's a common pattern I use for wizards.

It sucks and the code's ugly but it works.

We're going to create a 3-step wizard for creating a `User`.

```ruby
class User < ActiveRecord::Base

  validates_presence_of :email, :password
  validates_confirmation_of :password

end
```

schema:

    users (email, password, name, bio, created)

So in the first step we'll collect all the required `User` information: email
and password.

We can do this using #new and #create in our `UsersController`.

```ruby
class UsersController < ApplicationController

  def new
    @user = User.new
  end

  def create
    @user = User.new params[:user]
    if @user.save
      redirect_to edit_user_path(:id => @user,
                                 :step => 'b')
    else
      render :action => :new
    end
  end

end
```

But instead of redirecting to `#show` after a POST to `#create` we redirect to
`#edit`.  I also pass in the fact that I'm now on step 'b' (the second step) of
this wizard.

Here's `#edit`:

```ruby
def edit
  @step = params[:step]
  @user = User.find params[:id]
end
```

The view is more important.

In `app/views/users/edit.rhtml`:

```erb
<%= render :partial => @step, :locals => { :user => @user } %>
```

That's going to look for a partial named `_b.rhtml` when going to the second
step in the wizard.  This way we avoid conditional logic in this view.

In `app/views/users/_b.rhtml`:

```erb
<h2>Step Two</h2>

<%= error_messages_for :user %>
<% form_for :user,
            :url => user_path(:id => @user,
                              :step => @step),
            :html => { :method => :put } do |form| -%>

    <label for="user_name">Name</label>
    <%= form.text_field :name %>

    <%= submit_tag 'Submit' %>

<% end -%>
```

Here we collect some optional `User` information in a form that PUTs to #update,
passing along the current step in the wizard.

Here's #update:

```ruby
def update
  @step = params[:step]
  @user = User.find params[:id]
  if last_step?
    if @user.update_attributes params[:user].merge(:created => true)
      redirect_to user_path(@user)
    else
      render :action => :edit
    end
  else
    if @user.update_attributes params[:user]
      redirect_to edit_user_path(:id => @user,
                                 :step => @step.succ)
    else
      render :action => :edit
    end
  end
end

private

def last_step?
  params[:step] == 'c'
end
```

Now #update says if its the last step update it and redirect to #show, else
update it and redirect back to edit and increase the step by 1.

So you say What if I don't want a `User` to be shown on my site that hasn't
fully completed the wizard?  That is the purpose of the `created` boolean in the
`users` table.  The following block of code from #update sets `created` to true
if its the last step in the wizard:

```ruby
if last_step?
  if @user.update_attributes params[:user].merge(:created => true)
    redirect_to user_path(@user)
  else
    render :action => :edit
  end
else
```

That's the hack.  Having a boolean in your model to prevent incomplete models
from showing up on your site.  It's terrible because now all your `User` queries
will have to include the boolean:

```ruby
User.find :all,
  :conditions => 'created = true'
```

Here's the last step in the wizard:

In `app/views/users/_c.rhtml`:

```erb
<h2>Step 3 (last step)</h2>

<%= error_messages_for :user %>
<% form_for :user,
            :url => user_path(:id => @user,
                              :step => @step),
            :html => { :method => :put } do |form| -%>

    <label for="user_bio">Bio</label>
    <%= form.text_area :bio %>

    <%= submit_tag 'Submit' %>

<% end -%>
```

It collects more optional `User` in a form that PUTs to #update just like step
'b' (2) did.

You might be wondering why I chose letters instead of numbers for my wizard
stages.  Rails complains if you try to have a partial named 1.rhtml or 2.rhtml
for some reason.
