Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
create a functioning wizard for any model in three steps
branch: master

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.
app
config
db
lib
public
rails_generators
script
spec
test
.gitignore
CHANGELOG.rdoc
LICENSE
README.rdoc
Rakefile
init.rb
wizardly.gemspec

README.rdoc

wizardly

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

Resources

Examples

Source

  • git://github.com/jeffp/wizardly.git

Install

Description

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

Setup

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

Recommendations

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.

Example

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]
  ...
end

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

class SignupController < ApplicationController
  act_wizardly_for :user
end

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

http://github.com/jeffp/wizardly-examples

Usage

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}
end

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'
end

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.

Buttons

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
end

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

Callbacks

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] = ''
  end
end

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:

on_back
on_skip
on_cancel
...on_'custom_button'...
on_get
render_wizard_form

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:

on_post
on_back
on_skip
on_cancel
...on_'custom_button'...
on_errors
  render_wizard_form  # only if errors
on_next
on_finish

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
  ...
end
on_back(:step2, :step3, :step4) do
  ...
end
on_get(:all) do
  ...
end

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.html
      format.xml { render_xml(@step) }
    end
  end

  def render_xml(step)
    ...
  end
end

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
  end
end

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.

complete_wizard(some_model_path)

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

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

rake spec:all

Dependencies

  • validation_group is currently integrated (plugin not required)

  • ActiveRecord

  • ActionController

Something went wrong with that request. Please try again.