Coding

Ruby on Rails: step-by-step tutorial for creating a multi-step form in Rails using form objects and session storage

Here's a detailed step-by-step tutorial for creating a multi-step form in Ruby on Rails using form objects and session storage. This tutorial assumes you're familiar with basic Rails concepts and have Rails installed on your machine.

By applying these techniques, you can create multi-step forms that handle complex user input in a more user-friendly and manageable way. These forms are particularly useful in scenarios like sign-up processes, checkouts, or any situation where users need to provide a significant amount of information.

As a next step, consider enhancing this setup by adding features like AJAX for smoother transitions between steps, or integrating third-party APIs for real-time data validation or suggestions. This tutorial serves as a strong foundation for building more advanced and user-centric forms in your Rails applications.


 
Step 1: Setting Up the Rails Application

1.1 Initialize a New Rails App

First, create a new Rails application:
rails new multi_step_form
cd multi_step_form

1.2 Generate Models
Let’s assume our multi-step form collects a user’s details across three steps: User, Profile, and Address.

Generate the models: 
rails generate model User email:string password_digest:string
rails generate model Profile first_name:string last_name:string user:references
rails generate model Address street:string city:string zip:string user:references

 Run the migrations: 
rails db:migrate

 1.3 Add bcrypt for Secure Passwords 

 To handle passwords securely, we’ll use the bcrypt gem. Add it to your Gemfile It is usually preinstalled by rails already: 
# Gemfile
gem 'bcrypt', '~> 3.1.7'

 Run bundle install: 
bundle install

 Update the User model to handle passwords: 
# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_one :profile
  has_one :address
end

 Step 2: Creating the Form Objects

 We’ll create separate form objects for each step in the process. These form objects will handle validations and data encapsulation. 

 Create a forms directory in app to store our form objects: 
mkdir app/forms

 Step 1 Form Object: UserForm 
# app/forms/user_form.rb
class UserForm
  include ActiveModel::Model

  attr_accessor :email, :password, :password_confirmation

  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :password, presence: true, confirmation: true
end

 Step 2 Form Object: ProfileForm 
# app/forms/profile_form.rb
class ProfileForm
  include ActiveModel::Model

  attr_accessor :first_name, :last_name

  validates :first_name, :last_name, presence: true
end


 Step 3 Form Object: AddressForm 
# app/forms/address_form.rb
class AddressForm
  include ActiveModel::Model

  attr_accessor :street, :city, :zip

  validates :street, :city, :zip, presence: true
end

 Step 3: Managing State with Session Storage

3.1 Update the Controller
Generate a controller to handle the multi-step form:
rails generate controller Registration new_user new_profile new_address create

 Now, modify the RegistrationController: 
# app/controllers/registration_controller.rb
class RegistrationController < ApplicationController
  def new_user
    @user_form = UserForm.new(session[:user_form])
  end

  def new_profile
    redirect_to new_user_registration_path unless session[:user_form]
    @profile_form = ProfileForm.new(session[:profile_form])
  end

  def new_address
    redirect_to new_user_registration_path unless session[:user_form] && session[:profile_form]
    @address_form = AddressForm.new(session[:address_form])
  end

  def create
    @user_form = UserForm.new(session[:user_form])
    @profile_form = ProfileForm.new(session[:profile_form])
    @address_form = AddressForm.new(session[:address_form])

    if @user_form.valid? && @profile_form.valid? && @address_form.valid?
      user = User.create!(
        email: @user_form.email,
        password: @user_form.password
      )
      user.create_profile!(
        first_name: @profile_form.first_name,
        last_name: @profile_form.last_name
      )
      user.create_address!(
        street: @address_form.street,
        city: @address_form.city,
        zip: @address_form.zip
      )

      reset_session
      redirect_to root_path, notice: 'Registration completed successfully!'
    else
      render :new_user
    end
  end

  def update_user
    @user_form = UserForm.new(user_form_params)
    if @user_form.valid?
      session[:user_form] = user_form_params
      redirect_to new_profile_registration_path
    else
      render :new_user
    end
  end

  def update_profile
    @profile_form = ProfileForm.new(profile_form_params)
    if @profile_form.valid?
      session[:profile_form] = profile_form_params
      redirect_to new_address_registration_path
    else
      render :new_profile
    end
  end

  def update_address
    @address_form = AddressForm.new(address_form_params)
    if @address_form.valid?
      session[:address_form] = address_form_params
      redirect_to create_registration_path
    else
      render :new_address
    end
  end

  private

  def user_form_params
    params.require(:user_form).permit(:email, :password, :password_confirmation)
  end

  def profile_form_params
    params.require(:profile_form).permit(:first_name, :last_name)
  end

  def address_form_params
    params.require(:address_form).permit(:street, :city, :zip)
  end
end

 Step 4: Building the Multi-Step Form Interface

4.1 Create Views for Each Step
Create views for each step:

Step 1: User Form
<!-- app/views/registration/new_user.html.erb -->
<h2>Step 1: User Information</h2>
<%= form_with model: @user_form, url: update_user_registration_path, method: :patch do |form| %>
  <div>
    <%= form.label :email %>
    <%= form.email_field :email %>
  </div>
  <div>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </div>
  <div>
    <%= form.label :password_confirmation %>
    <%= form.password_field :password_confirmation %>
  </div>
  <%= form.submit "Next" %>
<% end %>

 Step 2: Profile Form 
<!-- app/views/registration/new_profile.html.erb -->
<h2>Step 2: Profile Information</h2>
<%= form_with model: @profile_form, url: update_profile_registration_path, method: :patch do |form| %>
  <div>
    <%= form.label :first_name %>
    <%= form.text_field :first_name %>
  </div>
  <div>
    <%= form.label :last_name %>
    <%= form.text_field :last_name %>
  </div>
  <%= form.submit "Next" %>
<% end %>

 Step 3: Address Form 
<!-- app/views/registration/new_address.html.erb -->
<h2>Step 3: Address Information</h2>
<%= form_with model: @address_form, url: update_address_registration_path, method: :patch do |form| %>
  <div>
    <%= form.label :street %>
    <%= form.text_field :street %>
  </div>
  <div>
    <%= form.label :city %>
    <%= form.text_field :city %>
  </div>
  <div>
    <%= form.label :zip %>
    <%= form.text_field :zip %>
  </div>
  <%= form.submit "Finish" %>
<% end %>

4.2 Routing

Add the necessary routes to your config/routes.rb file:
# config/routes.rb
Rails.application.routes.draw do
  root "welcome#index"

  resource :registration, only: [] do
    get :new_user, on: :collection
    patch :update_user, on: :collection

    get :new_profile, on: :collection
    patch :update_profile, on: :collection

    get :new_address, on: :collection
    patch :update_address, on: :collection

    get :create, on: :collection
  end
end

 Step 5: Testing the Multi-Step Form 

5.1 Unit Tests for Form Objects
Write tests for each form object to ensure validations work correctly:
# test/forms/user_form_test.rb
require "test_helper"

class UserFormTest < ActiveSupport::TestCase
  test "valid user form" do
    form = UserForm.new(email: "[email protected]", password: "password", password_confirmation: "password")
    assert form.valid?
  end

  test "invalid without email" do
    form = UserForm.new(password: "password", password_confirmation: "password")
    assert_not form.valid?
  end
end

5.2 Integration Tests
Write integration tests to simulate the user moving through the form:
# test/integration/registration_flow_test.rb
require "test_helper"

class RegistrationFlowTest < ActionDispatch::IntegrationTest
  test "successful registration" do
    get new_user_registration_path
    assert_response :success

    patch update_user_registration_path, params: {
      user_form

we've walked through the process of creating a multi-step form in a Ruby on Rails application using form objects, session storage, and service objects. By breaking down the form into manageable steps, we enhanced the user experience, allowing users to input complex data in a structured and intuitive way. 

1 comment
    • Tony
    • September 03, 2024

    This was so helpful thanks a lot and cheers this tutorial was so detailed and educational thanks again.

Leave a Reply

Your email adress will not be published ,Requied fileds are marked*.

Hi, I'm Kenroy Reid

Hello there

More Form Aurthor
Categories

Get The Best Blog Stories into Your inbox!

Sign up for free and be the first to get notified about new posts.