patternrubyrailsMinor
Developing a multistep form
Viewed 0 times
formmultistepdeveloping
Problem
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
This solved the problem. But I had to manage the case of errors in the form, so I ended up with this code.
Where in the
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.
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
endWhere 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.
Solution
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:
app/views/profiles/new.html.haml:
app/views/profiles/edit.html.haml:
app/views/profiles/_form.html.haml:
This is a very common pattern in rails apps that you can also see being used in the rails guides. The
Whenever updating or saving a record fails, the controller will render the appropriate view (
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
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.
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
endapp/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.submitThis 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.
Code Snippets
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%h2 New Profile
= render partial: 'form'%h2 Edit Profile
= render partial: 'form'= 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.submitContext
StackExchange Code Review Q#935, answer score: 5
Revisions (0)
No revisions yet.