ruby erb 118 lines · 3 tabs

Hotwire-powered multi-step forms

Jordan Lee Jan 2026
3 tabs
class WizardsController < ApplicationController
  before_action :load_draft

  def step_1
    render_step(1)
  end

  def update_step_1
    if update_draft(step_1_params)
      redirect_to wizard_step_2_path
    else
      render_step(1, status: :unprocessable_entity)
    end
  end

  def step_2
    render_step(2)
  end

  def update_step_2
    if update_draft(step_2_params)
      redirect_to wizard_step_3_path
    else
      render_step(2, status: :unprocessable_entity)
    end
  end

  def step_3
    render_step(3)
  end

  def create
    if @draft.update(step_3_params.merge(completed: true))
      session.delete(:wizard_draft_id)
      redirect_to @draft, notice: 'Application submitted successfully!'
    else
      render_step(3, status: :unprocessable_entity)
    end
  end

  private

  def load_draft
    @draft = WizardDraft.find_or_create_by(id: session[:wizard_draft_id], user: current_user)
    session[:wizard_draft_id] = @draft.id
  end

  def update_draft(params)
    @draft.update(params)
  end

  def render_step(step, status: :ok)
    render "wizards/step_#{step}", locals: { draft: @draft }, status: status
  end

  def step_1_params
    params.require(:wizard_draft).permit(:name, :email, :phone)
  end

  def step_2_params
    params.require(:wizard_draft).permit(:address, :city, :state, :zip)
  end

  def step_3_params
    params.require(:wizard_draft).permit(:preferences, :notes)
  end
end
3 files · ruby, erb Explain with highlit

Multi-step forms traditionally require complex JavaScript state management, but Hotwire makes them simple. Each step is a separate controller action that renders a Turbo Frame containing the current step's fields. Navigation between steps updates only the frame, preserving completed data in the session or a draft record. I validate each step server-side before allowing progress to the next. Back/forward buttons work naturally with Turbo's history management. This approach keeps form logic in Rails—validation, defaults, conditional fields—while delivering a SPA-like experience. The form submits normally on the final step, and server-side validation is the single source of truth throughout.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Hotwire-powered multi-step forms — share card
Link copied