create a functioning wizard for any model in three steps
Switch branches/tags
Nothing to show
Pull request Compare This branch is 22 commits behind jeffp:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



wizardly creates a multi-step wizard for any ActiveRecord model in three steps.




  • git://



wizardly builds on Alex Kira's validation_group plugin code to DRY up the Rails MVC implementation of a wizard. In three easy steps, wizardly produces the scaffolding and controller of a multi-step wizard.

Features include:

  • Model-based definition

  • Wizard controller macro

  • Wizard scaffolding generator

  • Default wizard buttons

  • Custom button creation

  • Page and button callbacks


Put the following in your application's config block in config/environment.rb

config.gem 'jeffp-wizardly', :lib=>'wizardly'

and run the install gems rake task on your application

rake gems:install

For any rails app, run the following to install wizardly rake tasks (optional)

./script/generate wizardly_app


Wizardly uses sessions. It is recommended you use a session store other than the default cookies store to eliminate the 4KB size restriction. To use the active record store, add this line to your environment.rb

config.action_controller.session_store = :active_record_store

And set up the sessions table in the database

rake db:sessions:create
rake db:migrate

Use the MemCached session store for higher performance requirements.


Create a working controller wizard for any model in 3 steps. Here's how:

Step 1: Define validation groups for your model.

class User < ActiveRecord::Base    
  validation_group :step1, :fields=>[:first_name, :last_name]
  validation_group :step2, :fields=>[:age, :gender]

Step 2: Tell your controller to act 'wizardly'.

class SignupController < ApplicationController
  act_wizardly_for :user

Step 3: Generate a 'wizardly' scaffold for your controller.

./script/generate wizardly_scaffold signup

You are ready to go.

General usage and configuration of wizardly follows. See the examples at


Default Behavior and Scaffolding Configuration

The wizardly_scaffold generator produces an html view for each validation_group declared in the model. The wizard progresses to each page in the same order of the validation_group(s) in the model. Buttons for navigating the wizard are included on each page.

You can view a wizardly controller's configuration by using the wizardly:config rake task. First you'll need to install it if you already have not

./script/generate wizardly_app

Then you can call the rake task for any controller configured with the 'act_wizardly_for' macro

rake wizardly:config name=signup

Substitute the name of any wizardly controller for 'signup'.

Completing and Canceling

Once a user has entered a wizard, there are two ways they can leave, either by completing or canceling the wizard.

The above example is pretty simple. In fact, no redirects have been defined for completing or canceling the wizard in the above example so the wizardly controller uses the HTTP_REFERER for both cases. What if there is no referer? The controller raises a RedirectNotDefined error. Let's remedy this problem with some options in the macro.

class SignupController < ApplicationController
  act_wizardly_for :user, :completed=>'/main/finished', 
    :canceled=>{:controller=>:main, :action=>:canceled}

Now whether the user completes or cancels the wizard, the controller knows how to redirect. Alternately, if you want to redirect to the same page for both cases

class SignupController < ApplicationController
  act_wizardly_for :user, :redirect=>'/main'

Options For act_wizardly_for

Here's a list of options you can use in the macro

:completed => '/main/finished'
:canceled => {:controller=>'main', :action=>'canceled'}
:skip => true
:guard => false
:mask_fields => [:password, :password_confirmation] (by default)
:persist_model => {:once|:per_page}
:form_data => {:sandbox|:session}

Setting the :skip option to true tells the scaffold helpers to include or exclude a skip button on each page. The :mask_fields options tells the scaffold generator which fields to generate as 'type=password' fields. :persist_model and :form_data are explained below.

Preserving Form Field Data

The :form_data option controls how the form data is preserved between page requests that call outside the wizard controller. The default option setting, :session, keeps the form data until the wizard is complete regardless of whether the user leaves the wizard and returns later. The form data is preserved for the life of the session or until the user completes the wizard.

The other option setting, :sandbox, clears the form data whenever the user leaves the wizard before the wizard is complete. This includes pressing a :cancel button, a hyperlink or plainly navigating somewhere else. Upon returning to the wizard, the form is reset and the user starts fresh.

The form data is always cleared once the user has completed the wizard and the database record has been created.

Guarding Wizard Entry

The :guard option controls how a user may enter the wizard. If set to true, the default, the wizard is guarded from entry anywhere except the first page. The wizard controller will automatically redirect to the first page. When set to false, entry may occur at any point. This may be useful for testing purposes and instances where the application needs to navigate away and return to the wizard.

The guarding behavior works a little differently depending on the :form_data setting. When :form_data is set to :session (the default behavior), guarding only occurs for the initial entry. Once a user has entered the form and started it, while form data is being kept, the application may thereafter enter anywhere. On the contrary, if :form_data is set to :sandbox, entry is always guarded, and once the user leaves the wizard, entry may only occur at the initial page (as the form data has been reset).

Saving The Model

The :persist_model option controls how the model is saved, either :once or :per_page. The default option :once, only saves the model when the wizard is completed, by the user pressing a :finish button or :next button on the final page. This method prevents numerous incomplete models and possibly invalid models being saved to the database.

The other option setting, :per_page, saves the model incrementally for each time the form data validates as the user moves through the pages.


The wizardly controller defines five default button functions: next, back, skip, cancel, and finish. All but :skip are used in the scaffolding by default. You can add :skip functionality to all pages by adding an option to the macro

class SignupController < ApplicationController
  act_wizardly_for :user, :redirect=>'/main', :skip=>true

You can create, customize and change buttons for any controller and any page. See the Advanced Configuration section.


The wizard macro 'act_wizardly_for' creates controller actions for each page. The processing is standard for a wizard. Hooks or callback macros are available for changing or interrupting the processing. Here's an example. Say our model declares a :step4 validation_group with :username, :password, and :password_confirmation fields. We'd like to handle this step specifically.

class SignupController < ApplicationController
  act_wizardly_for :user, :redirect=>'/main'

  on_errors(:step4) do
    #clear the password field if errors
    @user[:password] = ''
    @user[:password_confirmation] = ''

The above hook macro is only called if :step4 does not validate according to its validation_group.

Every page has a set of callback macros. Here's the list.

on_get(:step)         # called just after creating model instance and before rendering a GET request
on_post(:step)        # called just after creating the model instance for a POST request
on_errors(:step)      # called after on_post callback if the form is invalid according to validation group
on_next(:step)        # called on a valid form when :next button is pressed
on_back(:step)        # called when the :back button is pressed
on_cancel(:step)      # called when the :cancel button is pressed
on_finish(:step)      # called on a valid form when the :finish button is pressed
on_skip(:step)        # called when the skip button is pressed

The block for each hook is called in the controller context, thus giving it access to all controller variables and methods just as any controller action method. Each callback has access to the model through an instance variable using the model's name. So for instance if our model is :user, the instance variable is '@user'. Each wizard page action also defines a @title, @description and @step instance variable. These are primarily used for scaffolding. The @step holds the name of the current validation group, ie. :step1, step2, … in our examples.

Notice the last five callbacks are related to the default wizard buttons. Each callback gives the developer a chance to intervene before the impending render or redirect caused by a button click in the view.

Important: Method names for button callbacks will change if you change the name of the button. For general use, this is not an issue. See the Advanced Configuration section.

The order of callbacks for a GET request is as follows:


Of course, only one of the button callbacks (back, skip or cancel) may occur for any single request, and any one of them may keep the on_get from being called if they render or redirect. :next and :finish button callbacks only occur for POST requests. The on_get callback occurs just before rendering, and may itself render or redirect. The on_get callback is an opportunity to modify or check the model fields before rendering.

The order of callbacks for a POST request is as follows:

  render_wizard_form  # only if errors

In contrast to the on_get callback, the on_post callback occurs first. It is an opportunity to modify the model instance values (the model instance has already been created from the post parameters). The on_post method does not permit any rendering or redirecting since the back, skip and cancel buttons have not been evaluated and may require precedence over flow control. on_errors is called only for an invalid form and the render callback (see below) is called only if there are errors. Either on_next or on_finish are called if the form is valid. on_next is the default if a 'Finish' button is not pressed. on_next by default moves to the next page, while on_finish saves the model and redirects to the :completed redirect.

The argument of the callback macro indicates for which pages the block should be called. You can indicate as many pages or :all as shown.

on_post(:step1) do
on_back(:step2, :step3, :step4) do
on_get(:all) do

Indicating a form that does not exist raises a CallbackError.

Rendering Callback

For anyone needing to handle rendering in a special way, wizardly provides a render call back for this.

class SignupController < ApplicationController
  act_wizardly_for :user, :redirect=>'/main'

  def render_wizard_form
    respond_to do |format|
      format.xml { render_xml(@step) }

  def render_xml(step)

Completing the Wizard Programmatically

Perhaps you want to complete a wizard based off of a test instead of a button click. You can do this in your callbacks by calling the complete_wizard method.

on_next(:step4) do
  if (test_radio_button)

Complete wizard will save the model and redirect to the :completed redirect setting. You can change the redirect dynamically by passing it to the method.


Creating Scaffolds

Wizard scaffolds can be created for any wizardly controller (one using the acts_wizardly_for macro).

./script/generate wizardly_scaffold controller_name --haml

The wizardly_scaffold generator will create HTML view scaffolds by default. Append a –haml option to create scaffolds in HAML.

Sometimes you have already edited views from a scaffold but want to regenerate the scaffold because of changes to your model without overwriting the current views. Use the –underscore option to create corresponding views with an underscore prefixing each page.

./script/generate wizardly_scaffold controller_name --underscore

You can create a scaffold using image_submit_tag by doing the following:

./script/generate wizardly_scaffold controller_name --image_submit

Default button images are provided under the public/images/wizardly/ directory.

Advanced Configuration

To be provided


Testing uses RSpec and Webrat. Make sure you have the gems installed. Then to test the development code run the following:

rake spec:all


  • validation_group is currently integrated (plugin not required)

  • ActiveRecord

  • ActionController