# Developing a multistep form

I implemented a multistep form with the method described by Ryan Bates in ep217 but I had some weird behavior when refreshing or moving between the steps Acts_as_good_style (old but still good) has a tip redirect when moving on that lead me to the change in the code that follows.

In a nutshell, it says that if you're in the create action you should not render new (as I was doing).

This solved the problem. But I had to manage the case of errors in the form, so I ended up with this code.

#profiles_controller.rb

def create
# [...] save etc [...]

# render
if @profile.new_record?
# render 'new' # OLD
session[:profile_valid] = @profile.errors.blank?  # NEW
redirect_to new_profile_path # NEW
else
# [...]
end
end

def new
@profile = Profile.new(session[:profile_params])
# [...]

# rebuild errors (see create)
# check false because first time is nil and no error have to be displayed
@profile.valid? if session[:profile_valid] == false
session[:profile_valid] = true
end


Where in the new action I reload the errors otherwise lost depending on session[:profile_valid], that works fine.

BUT this way doesn't look very good to me and I would appreciate to have your opinion, OR how do you manage your controllers in multispets forms?

What was the strange behavior? Refreshing or going back and forth through the steps sometimes you jump to the wrong page, not clear what the logic is, probably depends on the validations in the model and the params hash.

I can't speak for that particular Railscast episode, but rails provides an idiomatic way to deal with validation errors. Taking your code as an example, I'd change it to look like this (using Rails 4.2):

app/controllers/profiles_controller.rb:

class ProfilesController < ActionController::Base
def show
@profile = Profile.find(...)
end

def new
@profile = Profile.new(profile_params)
end

def create
@profile = Profile.new(profile_params)

if @profile.save
redirect_to profile_path(@profile)

else
render :new
end
end

def edit
@profile = Profile.find(...)
end

def update
@profile = Profile.find(...)

if @profile.update_attributes(profile_params)
redirect_to profile_path(@profile)

else
render :edit
end
end

private

def profile_params
# call params.require(:profile).permit(...) to whitelist attributes as needed
params.require(:profile)
end
end


app/views/profiles/new.html.haml:

%h2 New Profile
= render partial: 'form'


app/views/profiles/edit.html.haml:

%h2 Edit Profile
= render partial: 'form'


app/views/profiles/_form.html.haml:

= form_for(@profile) do |f|
- # Display any form errors, if any
- if @profile.errors.any?
.errors
%h2 Errors
%ul
- @profile.errors.full_messages do |message|
%li= message

- # Assuming profiles have first_name and last_name as attributes
%section
= f.label :first_name
= f.text_field :first_name

%section
= f.label :last_name
= f.text_field :first_name

%section
= f.submit


This is a very common pattern in rails apps that you can also see being used in the rails guides. The form_for helper is smart enough to generate the correct path to submit the form to depending on weather or not the @profile instance variable contains a record that has or has not been persisted. Persisted records (new ones or those with errors) will be posted to create. Records being updated will be set to whatever the update action/path for that controller is.

Whenever updating or saving a record fails, the controller will render the appropriate view (new or edit) and use the record that failed validation when rendering the form. From here, your view can inspect the record and display any errors if they exist (@profile.errors).

This won't fix an issue of a user navigating backwards and forwards through your form, but it should reduce the need to do so and it'll let you avoid weirdness you may have encountered while persisting form data within the session variable (which gets serialized and de-serialized into whatever your session store is during each request).

PS: I find that thinking of multi-step forms with more than one or two steps as state machines helps greatly in trying to conceptualize the different states and transitions you need to handle when you need to determine at what point in a form a particular session should be in. To that effect, I would highly recommend checking out the wicked gem which does a lot of this grunt work for you.

• I like your answer! I'll wait a day or two before granting the bounty. Thanks for looking it over! Dec 19 '14 at 20:18
• Thanks. Relatively simple question though, I'm surprised nobody has chimed in yet. Jan 2 '15 at 7:47