Rails 5.2 to 8.0: A Practical Upgrade Guide
Lessons learned from upgrading PropertyWebBuilder through three major Rails versions with AI assistance
The Challenge
Upgrading a Rails application across multiple major versions isn't just about running bundle update rails. When PropertyWebBuilder sat at Rails 5.2 for nearly five years, the technical debt accumulated like compound interest:
- Breaking changes across Rails 6, 7, and 8
- Deprecated gems that needed modern replacements
- Test failures cascading from API changes
- Asset pipeline migration (Sprockets → Webpacker → Importmaps)
- Dependency conflicts with 50+ gems
This guide shares the practical lessons from taking a real-world Rails engine through this journey in 2024.
Strategy: Incremental Upgrades
Don't jump straight to Rails 8. The temptation is real, but you'll drown in simultaneous breaking changes.
Our approach:
Rails 5.2 → 6.0 → 6.1 → 7.0 → 7.1 → 8.0
Each step took 1-3 days. Total upgrade time: ~2 weeks (would have been 2+ months without AI assistance).
Phase 1: Rails 5.2 → 6.0
The Biggies
1. Zeitwerk Autoloading
Rails 6 introduced Zeitwerk, completely changing how Rails loads constants.
Problem: Nested modules broke unless file structure matched exactly.
# Before (worked in Rails 5.2)
# app/models/pwb/property.rb
class Property < ApplicationRecord
end
# After (required in Rails 6+)
# app/models/pwb/property.rb
module Pwb
class Property < ApplicationRecord
end
end
Fix: Run rails zeitwerk:check constantly. It became our best friend.
2. ActiveRecord Changes
.update_attributes→.update(hard deprecation).save(validate: false)behavior changed- Belongs_to required by default (configure or fix)
3. ActionMailer Configuration
# Before
config.action_mailer.delivery_method = :smtp
# After - more explicit configuration required
config.action_mailer.smtp_settings = {
address: ENV['SMTP_ADDRESS'],
port: ENV['SMTP_PORT'],
# ... more explicit settings
}
Testing is Non-Negotiable
We had ~200 RSpec tests. About 40% broke in the 5.2→6.0 jump.
Common failures:
- Controller tests needed response.parsed_body instead of JSON.parse(response.body)
- Fixture loading behavior changed
- assigns(:variable) removed from controller tests
Time saved: AI helped translate old test syntax to new patterns in minutes, not hours.
Phase 2: Rails 6.1 → 7.0
The JavaScript Apocalypse
Rails 7 dropped Webpacker by default. For PWB, this meant rethinking our entire asset strategy.
Our decision: Move from Vue.js SPA to Rails views with Hotwire.
Why?
1. PWB is content-heavy, not interaction-heavy
2. SEO matters for property listings
3. Simpler deployment (no separate JS build)
4. Fewer moving parts = easier maintenance
The migration:
# Before: Vue.js component
<pwb-property-card :property="property"></pwb-property-card>
# After: Rails partial with Turbo Frames
<%= turbo_frame_tag "property_#{property.id}" do %>
<%= render 'properties/card', property: property %>
<% end %>
Brutal honesty: This was the hardest part. We lost some interactivity. But we gained simplicity and maintainability.
New Defaults
# config/application.rb
config.load_defaults 7.0
# This enabled:
# - Button_to uses Turbo by default
# - ActiveStorage variant preprocessor
# - Digest class changes for cookies
Pro tip: Enable new defaults incrementally, not all at once. Comment out the line, add each setting manually, test.
Phase 3: Rails 7.0 → 8.0
Kamal & Solid Queue
Rails 8 ships with deployment tools (Kamal) and background job defaults (Solid Queue).
For PWB: We stuck with Heroku deployment (for now) but evaluated:
# config/environments/production.rb
# Rails 8 uses Solid Queue by default
config.active_job.queue_adapter = :solid_queue
# We stayed with Sidekiq
config.active_job.queue_adapter = :sidekiq
Why? Existing infrastructure. Solid Queue is compelling for new projects.
Authentication Changes
Rails 8 includes authentication generators. PWB already had Devise, but new projects should consider:
rails generate authentication
Clean, minimal, built-in. If we were starting fresh, we'd use it.
Rubocop & Modern Ruby
Rails 8 assumes Ruby 3.1+. We moved to Ruby 3.2.
Breaking changes:
- Psych YAML changes (aliases behavior)
- Keyword argument strictness
- Some stdlib gems extracted
The AI Multiplier
Where AI Saved Weeks
1. Gem Compatibility Research
Instead of manually checking each gem's GitHub for Rails 8 support:
Prompt: "Check if acts_as_tenant, friendly_id, and
cocoon are compatible with Rails 8.0"
Response: Detailed compatibility matrix with
alternatives suggested.
2. Test Fixing at Scale
200+ tests broke. AI helped batch-fix similar patterns:
# AI spotted the pattern across 50+ tests
# Old
expect(assigns(:property)).to eq(property)
# New
expect(controller.instance_variable_get(:@property)).to eq(property)
3. Deprecation Warnings
Rails upgrade = deprecation warning hell. AI helped triage:
DEPRECATION WARNING: update_attributes is deprecated...
Prompt: "Find all uses of update_attributes in app/
and suggest replacements"
4. Configuration Deep Dives
Prompt: "Explain what config.load_defaults 8.0
changes and potential breaking changes for a
multi-tenant Rails engine"
Got detailed explanations instead of hunting through changelogs.
Where AI Struggled
- Engine-specific issues (Rails Engines are quirky)
- Complex database migrations (still needed manual review)
- Performance regressions (AI can't benchmark)
Lessons Learned
1. Fix Tests First
Don't skip tests because "they're old." They catch regressions.
We spent 30% of upgrade time fixing tests. Worth every minute.
2. Dependency Lock Strategy
# Gemfile - staged approach
gem 'rails', '~> 6.1.0' # Lock minor version
# ... test everything
gem 'rails', '~> 7.0' # Then bump
Don't do gem 'rails', '>= 6.1' hoping for the best.
3. Read the Changelogs (Really)
AI summarizes well, but read the official Rails upgrade guides:
- https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html
Skim them before upgrading. You'll know what to expect.
4. Keep a Rollback Plan
We maintained Git branches for each Rails version:
git checkout -b rails-6.0
# ... upgrade work
# ... commit when stable
git checkout -b rails-6.1
Psychological safety: Knowing you can roll back reduces fear of trying things.
Gem-Specific Gotchas
acts_as_tenant (Multi-tenancy)
# Rails 5.2 - worked fine
ActsAsTenant.current_tenant = account
# Rails 7+ - needed thread safety updates
ActsAsTenant.with_tenant(account) do
# scoped operations
end
Devise (Authentication)
Mostly worked, but:
- Update to 4.9+ for Rails 7
- Watch for Turbo conflicts (form submissions)
- data: { turbo: false } on auth forms
FriendlyId (Slugs)
# Update to 5.5+ for Rails 7
gem 'friendly_id', '~> 5.5'
# Regenerate initializer
rails generate friendly_id
ActiveAdmin
This was painful. ActiveAdmin lagged Rails 7 support.
Our solution:
1. Update to latest version (2.13+)
2. Replace custom pages with standard resources where possible
3. Fix Ransack query deprecations manually
The Results
Before Upgrade:
- Rails 5.2.8 (released 2022)
- Ruby 2.7
- ~40 security vulnerabilities
- No GitHub Actions support
- Difficulty recruiting contributors
After Upgrade:
- Rails 8.0.1
- Ruby 3.2.2
- 0 critical vulnerabilities
- Modern CI/CD
- Fresh contributor interest
Metrics:
- ⬆️ Test coverage: 65% → 78%
- ⬇️ Page load times: ~800ms → ~450ms (Turbo + modern Rails)
- ⬆️ Developer happiness: "Meh" → "Excited"
Should You Upgrade?
Yes, if:
- Your app is actively maintained
- You care about security
- You want modern gem ecosystem access
- You're recruiting contributors (outdated stack = harder)
Wait, if:
- App is in maintenance-only mode
- Rails 5.2 still gets security patches (until June 2024... already passed)
- You're planning a full rewrite anyway
For PWB: The upgrade was existential. Staying on Rails 5.2 meant slow death.
Tools That Helped
- rails_upgrade gem - checks deprecations
- RuboCop with Rails cops - catches patterns
- bundler-audit - security vulnerabilities
- AI assistants (Claude, GitHub Copilot) - pattern fixes, research
- Patience - seriously, don't rush
Next Steps for Your Upgrade
-
Audit current state
bash bundle audit rails zeitwerk:check bundle exec rspec # baseline -
Create upgrade branch
bash git checkout -b rails-6-upgrade -
Update Gemfile (one version)
ruby gem 'rails', '~> 6.0.0' -
Bundle update incrementally
bash bundle update rails bundle install -
Fix, test, commit
- Fix errors one at a time
- Run tests constantly
-
Commit working states
-
Repeat for next version
Conclusion
Upgrading Rails is like renovating a house while living in it. Messy, occasionally frustrating, but absolutely worth it.
The AI factor: What used to take solo developers months can now happen in weeks. Not because AI writes perfect code, but because it handles the tedious research, pattern matching, and boilerplate that used to consume 70% of upgrade time.
PropertyWebBuilder went from Rails 5.2 to 8.0 in about 2 weeks of focused work. Five years ago, that would have been a 2-3 month project.
The future of maintaining open source just got a lot more feasible.
Resources
- Rails Upgrade Guides
- PropertyWebBuilder Repo
- FastRuby Upgrade Service (if you need pro help)
- GoRails Rails 8 Screencasts
Want to see this in action? Check out the PropertyWebBuilder repository for the full upgrade commits and journey.
Questions? Find me on Twitter/X [@etewiah] or open a GitHub discussion.
PropertyWebBuilder — Open-source Rails engine for real estate websites