-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
176: Practice Exercise: Building a Multi-Step Wizard Form Backend
Where should I actually put the data while the user is moving between steps?
This is the first fork in the road. You have two real options: the session or a "draft" record in your database. For most onboarding flows—like a User Profile setup where you're collecting a username, then preferences, then a billing address—the session is your best bet. It's fast, and you don't clutter your database with half-finished accounts that people abandoned halfway through.
I usually wrap this in a helper method or a specialized session manager so the controller doesn't get messy. Here is how I'd structure the session storage for a multi-step sign-up:
# In your controller
def update_wizard_data
# We store everything under a single key to keep the session organized
session[:registration_data] ||= {}
session[:registration_data].merge!(wizard_params)
end
private
def wizard_params
# Only permit params relevant to the current step
params.require(:user).permit(:username, :email, :theme, :notifications_enabled, :street, :city)
end
Just a heads up: if you're building something massive—think a 20-page mortgage application—stop using the session. Large hashes in the session can lead to cookie overflow errors or slow requests. In those cases, create a DraftApplication model and save to the DB at every "Next" click.
How do I stop someone from jumping straight to the last step by changing the URL?
You can't trust the user to follow your intended flow. If your final step is /wizard/step-3, a curious user will just type that into the address bar. If your code expects data from step 1 and 2 to be present, you'll likely hit a NoMethodError or save a bunch of null values.
I handle this with a simple guard method. I check if the required keys for the previous steps exist in the session. If they don't, I boot them back to the start. It's a bit blunt, but it works.
def ensure_step_completed!(step_number)
required_keys = {
2 => [:username, :email], # To get to step 2, step 1 must be done
3 => [:theme, :notifications_enabled] # To get to step 3, step 2 must be done
}
keys = required_keys[step_number]
return if keys.nil? || keys.all? { |k| session[:registration_data]&.key?(k) }
redirect_to wizard_step_1_path, alert: "Please complete the previous steps first."
end
What's the cleanest way to save all this fragmented data into one record at the end?
Please, for the love of your future self, do not put the final persistence logic inside your controller action. By the time you reach the final "Submit" button, you're probably dealing with three different database tables (e.g., User, Profile, and BillingAddress). Putting that in a controller leads to a "Fat Controller" nightmare.
I always move this into a Service Object. The controller's only job should be to pass the session data to the service and handle the result. It keeps your logic testable and your controller lean.
# app/services/user_registration_service.rb
class UserRegistrationService
def initialize(data)
@data = data
end
def call
ActiveRecord::Base.transaction do
user = User.create!(
username: @data[:username],
email: @data[:email]
)
user.create_profile!(
theme: @data[:theme],
notifications: @data[:notifications_enabled]
)
user.create_billing_address!(
street: @data[:street],
city: @data[:city]
)
user
end
rescue ActiveRecord::RecordInvalid => e
# Return the error or a custom failure object
false
end
end
In the controller, it becomes a one-liner: UserRegistrationService.new(session[:registration_data]).call. If it returns a user, you clear the session and redirect to the dashboard. Clean and simple.
📋 Practical Task
Exercise: Implementing the Step-Guard for a Subscription Wizard
You are building a 3-step subscription wizard:
Step 1: Plan Selection (requires plan_id),
Step 2: User Details (requires email), and
Step 3: Payment Method (requires card_token).
Your task is to implement a verify_wizard_progress! method in the provided controller. The method must ensure that a user cannot access Step 2 without a plan_id in the session, and cannot access Step 3 without both a plan_id AND an email in the session.
class SubscriptionWizardController < ApplicationController
before_action :verify_wizard_progress!, only: [:step_2, :step_3]
def step_1; end
def step_2; end
def step_3; end
private
def verify_wizard_progress!
# TODO: Implement the logic here
# Use session[:subscription_data] to check for required keys
# Redirect to step_1_path if requirements aren't met
end
endThere are no comments for now.