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

  1. rails_upgrade gem - checks deprecations
  2. RuboCop with Rails cops - catches patterns
  3. bundler-audit - security vulnerabilities
  4. AI assistants (Claude, GitHub Copilot) - pattern fixes, research
  5. Patience - seriously, don't rush

Next Steps for Your Upgrade

  1. Audit current state
    bash bundle audit rails zeitwerk:check bundle exec rspec # baseline

  2. Create upgrade branch
    bash git checkout -b rails-6-upgrade

  3. Update Gemfile (one version)
    ruby gem 'rails', '~> 6.0.0'

  4. Bundle update incrementally
    bash bundle update rails bundle install

  5. Fix, test, commit

  6. Fix errors one at a time
  7. Run tests constantly
  8. Commit working states

  9. 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


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

GitHub · Articles · Twitter