views:

126

answers:

3

I'm building a multi-step form in rails. It's not javascript driven, so each page has its own controller action like "step1" "step2" etc. I know how to do multi-step wizards through JQuery but I don't know how to keep rails validations per page without getting into javascript, hence this way.

Anyways, my model is a User object but I'm storing all my variables in an arbitrary Newuser variable and using the following in the view:

<% form_for :newuser, :url => { :action => "step3" } do |u| %>

In the controller, I merge the current page's info with the overall hash using:

session[:newuser].merge!(params[:newuser])

This works great except that if the user clicks back to a previous page, the fields are no longer populated. How do I keep them populated? Do I need to change the object in the form_for to somehow refer to the session[:newuser] hash?

EDIT:

I guess I'm looking for more info on how form_for autopopulates fields within the form. If it's not built around a model but an arbitrary hash (in this case, session[:newuser]), how do I get it to autopopulate?

A: 

This is how we did a multi-step form with validations

class User < ActiveRecord::Base

  attr_writer :setup_step

  with options :if => :is_step_one? do |o|
    o.validates_presence_of :name
  end

  with options :if => :is_step_two? do |o|
    o.validates_presence_of :email
  end

  def setup_step
    @setup_step || 1
  end

  def is_step_one?
    setup_step == 1
  end

  def is_step_two?
    setup_step == 2
  end

  def last_step?
    is_step_two? #change this to what your last step is
  end
end

Then in the controller:

UsersController
  SETUP_STEPS{1 => 'new', 2 => 'step_two'}

  def new
    @user = User.new
  end

  def step_two
  end

  def create
    @user = User.new(params[:user])

    if [email protected]?
      render :action => SETUP_STEPS[@user.setup_step]
    elsif @user.last_step?
      @user.save
      #do stuff
    else
      render :action => SETUP_STEPS[@user.setup_step]
    end
  end
end

And then in your forms, they are like like any other rails form with one exception, you will need a hidden field to hold the values from your previous steps.

- form_for @user, :url => users_path do |f|
  - [:login, :password].each do field
    = f.hidden_field field
Geoff Lanotte
There's a lot of good ideas that you have... thanks Geoff. My one issue is that I have credit card info and other info I don't want in hidden fields, hence the session data. But for the rest of the stuff it sounds good! :)
Kevin
Understood, you could tuck the credit card away in a session variable and pull it in before the save, that way the credit card is the one outlier. Or you could just save the credit card for the last step :)
Geoff Lanotte
A: 

What about still using a class for your population?

class User
  attr_accessor :credit_card, :name, :likes_fried_chicken

  def initialize(options = {})
    options.each do |key, value|
      self.send("#{key}=", value)
    end
  end
end

you could use some tableless model functions here if you wanted to include some validations.

Then in your controller steps:

def step_one
  @user = User.new(session[:new_user])
end

your forms should continue to work.

Another option is just to set the value of the form objects directly from your session hash

- form_for :user, :url => step_2_path do |f|
  = f.text_field :name, :value => session[:new_user][:name]
Geoff Lanotte
Geoff, your solution is close to what I'm using by setting a new user for each controller... I guess I just wanted to know how theoretically to autopopulate a form based upon an arbitrary non-model hash. If I don't get another answer, I'll give you the 50 pts. :)
Kevin
`self.send("#{key}=", value)` appears to bypass the ActiveRecord attr_protected and attr_accessible mass-assignment safety features: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001796
sarnold
A: 

do you know about this ? http://railscasts.com/episodes/217-multistep-forms it is a realy great tutorial ;)

Oluf Nielsen
Yeah I do... I'm a big fan of Ryan's railscasts but that one in particular I don't like his methodology, too convoluted of an approach in my mind.
Kevin