Rails 8 Upgrade Guide 2025: Step-by-Step Instructions for a Smooth Transition

Chetan MittalChetan Mittal
11 min read

Upgrading your Ruby on Rails application is essential for maintaining a secure, performant, and modern codebase.

With Rails 8 released in November 2024, developers are presented with opportunities to leverage new features, streamline their apps, and address technical debt.

However, Rails upgrades, especially major ones, require careful planning to avoid disruptions and ensure a smooth transition.

This guide provides a step-by-step approach to upgrading your Rails application in 2025.

Whether you’re moving up incrementally from Rails 6.x or making a leap from Rails 5 or earlier, these strategies will help you execute your upgrade successfully.


1. Assess Your Current Setup

Before starting the upgrade, evaluate the state of your application to understand its current dependencies, structure, and compatibility with newer Rails versions.

Checklist for Assessment

  • Rails Version: Identify your current version using rails -v. The larger the gap between your current version and Rails 8, the more complex the upgrade will be.

  • Ruby Version: Verify your Ruby version with ruby -v. Rails 8 may require a more recent Ruby version (e.g., 3.x or higher).

  • Gem Dependencies: Audit your Gemfile for outdated gems using bundle outdated. Pay attention to gems heavily tied to specific Rails versions.

  • Custom Code: Look for custom patches or workarounds in your codebase that might conflict with new Rails functionalities.

  • Test Coverage: Ensure your test suite is comprehensive. High test coverage is critical for identifying and fixing issues during the upgrade process.

Code Example: Version Checking Utility

# lib/tasks/version_check.rb
require 'rails'
require 'rubygems'
require 'bundler'

class RailsUpgradeCompatibilityChecker
  def self.check_current_environment
    puts "🔍 Rails and Ruby Environment Compatibility Check\n"

    # Ruby Version Check
    ruby_version = RUBY_VERSION
    ruby_major = ruby_version.split('.')[0..1].join('.')
    rails_recommended_ruby = '3.1'

    puts "Current Environment Details:"
    puts "------------------------"
    puts "Ruby Version:  #{ruby_version}"

    # Rails Version Check
    begin
      rails_version = Rails.version
      puts "Rails Version: #{rails_version}"
    rescue NameError
      puts "❌ Rails not loaded. Ensure you're running this in a Rails environment."
      return
    end

    # Gem Dependency Version Checks
    begin
      puts "Bundler Version: #{Bundler::VERSION}"
    rescue => e
      puts "Bundler version could not be determined: #{e.message}"
    end

    # Ruby Compatibility Assessment
    ruby_compatibility_check(ruby_major, rails_recommended_ruby)

    # Rails Version Compatibility
    rails_version_compatibility_check(rails_version)

    # Gem Compatibility Quick Scan
    gem_compatibility_scan
  end

  def self.ruby_compatibility_check(ruby_major, rails_recommended_ruby)
    puts "\n📊 Ruby Compatibility:"
    begin
      if Gem::Version.new(ruby_major) < Gem::Version.new(rails_recommended_ruby)
        puts "⚠️ Warning: Consider upgrading Ruby."
        puts "   Current Version:     #{ruby_major}"
        puts "   Recommended Version: #{rails_recommended_ruby}+"
        puts "   Potential Issues: Some Rails 8 features may not be fully supported"
      else
        puts "✅ Ruby version is compatible with Rails 8"
      end
    rescue => e
      puts "❌ Error in Ruby compatibility check: #{e.message}"
    end
  end

  def self.rails_version_compatibility_check(rails_version)
    puts "\n📊 Rails Version Compatibility:"
    begin
      major_version = rails_version.split('.').first.to_i

      if major_version < 8
        puts "⚠️ Upgrade Recommended:"
        puts "   Current Version: #{rails_version}"
        puts "   Target Version:  8.x"
        puts "   Suggested Action: Plan incremental upgrade path"
      elsif major_version == 8
        puts "✅ Running Latest Rails Version (8.x)"
      else
        puts "🤔 Running Unreleased/Experimental Rails Version"
      end
    rescue => e
      puts "❌ Error in Rails version compatibility check: #{e.message}"
    end
  end

  def self.gem_compatibility_scan
    puts "\n📦 Quick Gem Compatibility Scan:"
    begin
      # Use Bundler CLI to check for outdated gems
      outdated_output = `bundle outdated 2>&1`

      # Check for actual outdated gems, filtering out platform warnings
      outdated_lines = outdated_output.lines.select do |line|
        line.include?('*') &&
        !line.include?('tzinfo-data') &&
        !line.include?('Fetching gem metadata')
      end

      if outdated_lines.empty?
        puts "✅ All gems appear to be up-to-date"

        # Platform-specific suggestion
        if outdated_output.include?('tzinfo-data')
          puts "\n📝 Note: You have platform-specific dependencies."
          puts "   Run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`"
          puts "   to resolve platform-specific gem warnings"
        end
      else
        puts "⚠️ Potentially Outdated Gems Detected:"
        outdated_lines.each { |line| puts "   #{line.strip}" }
      end
    rescue => e
      puts "❌ Error scanning gem compatibility: #{e.message}"
      puts "   Raw output: #{outdated_output}"
    end
  end
end
# lib/tasks/version_check.rake
require_relative 'version_check'  # Explicitly require the file

namespace :app do
  desc "Check Rails and Ruby environment compatibility"
  task version_check: :environment do
    RailsUpgradeCompatibilityChecker.check_current_environment
  end
end
cd <your-app-path>
rails app:version_check

This script helps developers quickly assess their current environment's compatibility with Rails 8 by checking Ruby, Rails, and Gems versions.


2. Understand What’s New in Rails 8

Each new Rails release brings innovations, deprecations, and sometimes breaking changes.

Rails 8 is no exception, introducing updates that could simplify development but require adaptation.

Key Highlights of Rails 8 (As of 2024):

  • Enhanced Hotwire integration, making real-time applications faster and easier to build.

  • Expanded asynchronous processing capabilities for background jobs and tasks.

  • Improved Active Record associations for better performance and flexibility.

  • Deprecation of older APIs and removal of legacy components.

Review the Rails 8 release notes to identify changes relevant to your app.


3. Plan Incremental Upgrades

If your app is on an older version of Rails (e.g., 5.x or 6.x), avoid jumping directly to Rails 8.

Incremental upgrades reduce risk by introducing smaller changes at each step.

Why Incremental Upgrades?

  • They help you isolate issues specific to each version.

  • They make debugging easier and reduce the likelihood of introducing breaking changes.

  • They allow you to take advantage of improvements incrementally, rather than all at once.

  1. Rails 5.0 → 6.1

  2. Rails 6.0 → 6.1

  3. Rails 6.1 → 7.0

  4. Rails 7.0 → 7.1

  5. Rails 7.1 → 8.0

For apps already on Rails 7.x, the transition to Rails 8 should be more straightforward but still requires careful preparation.

Code Example: Incremental Upgrade Strategy

# Upgrade Example: User Authentication Model

# RAILS 5 VERSION
# app/models/user.rb
class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
  has_secure_password

  # Basic scopes
  scope :active, -> { where(active: true) }
end

# config/initializers/devise.rb (if using Devise)
Devise.setup do |config|
  config.secret_key = Rails.application.secrets.secret_key_base
end

# RAILS 6 TRANSITION
# app/models/user.rb
class User < ApplicationRecord
  # Added stronger validations
  validates :email, 
    presence: true, 
    uniqueness: { case_sensitive: false },
    format: { 
      with: URI::MailTo::EMAIL_REGEXP, 
      message: "must be a valid email format" 
    }

  has_secure_password

  # Enhanced scopes
  scope :active, -> { where(active: true) }
  scope :recent, -> { where('created_at >= ?', 30.days.ago) }
end

# config/initializers/new_framework_defaults_6.rb
Rails.application.config.action_dispatch.return_response_for_redirect_status = true
Rails.application.config.active_record.collection_cache_versioning = true

# RAILS 7 UPGRADE
# app/models/user.rb
class User < ApplicationRecord
  # Added authentication concerns
  include Authenticatable

  validates :email, 
    presence: true, 
    uniqueness: { case_sensitive: false },
    format: { 
      with: URI::MailTo::EMAIL_REGEXP, 
      message: "must be a valid email format" 
    }

  # Explicit password validation
  validates :password, 
    length: { minimum: 8 }, 
    if: -> { new_record? || changes[:password_digest] }

  # Enhanced relationship support
  has_many :authentication_tokens, dependent: :destroy

  # Class methods with improved query methods
  def self.find_by_email_case_insensitive(email)
    find_by("LOWER(email) = ?", email.downcase)
  end
end

# RAILS 8 FINAL VERSION
# app/models/user.rb
class User < ApplicationRecord
  # Leverage Rails 8 authentication patterns
  has_secure_password :recoverable

  # Advanced validations
  validates :email, 
    presence: true, 
    uniqueness: { case_sensitive: false },
    format: { 
      with: URI::MailTo::EMAIL_REGEXP, 
      message: "must be a valid email format" 
    }

  # Comprehensive password requirements
  validates :password, 
    length: { minimum: 12 },
    complexity: {
      require_special_characters: true,
      require_number: true,
      require_uppercase: true,
      require_lowercase: true
    }

  # Modern Rails 8 relationship definitions
  has_many :sessions, 
    class_name: 'UserSession', 
    dependent: :destroy

  # Advanced querying
  scope :inactive_for_months, ->(months) { 
    where('last_sign_in_at < ?', months.months.ago) 
  }

  # Encryption and security enhancements
  encrypts :email, deterministic: true

  # Standardized authentication methods
  def generate_reset_token
    # Secure token generation logic
    SecureRandom.urlsafe_base64(32)
  end
end

# Migration Example
# db/migrate/YYYYMMDDHHMMSS_enhance_user_model.rb
class EnhanceUserModel < ActiveRecord::Migration[8.0]
  def change
    # Rails 8 migration style
    create_table :users do |t|
      t.string :email, null: false, index: { unique: true }
      t.string :password_digest, null: false
      t.datetime :last_sign_in_at

      # Additional security columns
      t.integer :failed_attempts, default: 0
      t.datetime :locked_at

      t.timestamps
    end
  end
end

# Gemfile dependencies
gem 'bcrypt', '~> 3.1.20'  # Password hashing
gem 'secure_headers'       # Security middleware
gem 'rack-attack'          # Request throttling

Incremental upgrades are crucial because they allow developers to systematically evolve their Ruby on Rails applications without risking a catastrophic, all-at-once migration.

By moving through each version step-by-step—as demonstrated in the User model example above—teams can progressively adapt to new framework features, security enhancements, and architectural changes, identifying and resolving compatibility issues at each stage rather than facing a overwhelming, high-risk "big bang" upgrade.

In the above example the User model's transformation from Rails 5 to Rails 8 illustrates this perfectly.

Each version introduces more sophisticated authentication, validation, and security mechanisms.

What starts as a basic model with simple password validation becomes a robust, encryption-enabled, multi-layered authentication system.

This incremental approach not only minimizes potential breaking changes but also allows developers to leverage new framework capabilities gradually, ensuring application stability, improving code quality, and maintaining ongoing security and performance improvements.

Key Upgrade Principles

  • Upgrade one major version at a time

  • Maintain comprehensive test coverage

  • Use continuous integration

  • Incrementally resolve deprecation warnings


4. Audit and Update Dependencies

Dependencies are often the trickiest part of a Rails upgrade.

Many gems have version constraints tied to specific Rails releases.

Steps to Handle Dependencies

  1. Run bundle outdated to identify outdated gems.

  2. Update core gems incrementally. For example, update devise, sidekiq, and other critical gems to versions compatible with Rails 8.

  3. Replace abandoned or unmaintained gems with active alternatives.

Pro Tip

When updating gems, review changelogs and documentation for compatibility notes and breaking changes.

This can save significant debugging time.


5. Upgrade the Rails Framework

Once your dependencies are updated and your code is prepared, it's time to upgrade Rails itself.

Steps to Upgrade Rails

  1. Backup First: Ensure your database and codebase are securely backed up.

  2. Update Rails: Modify your Gemfile to specify the desired Rails version, then run bundle update rails.

  3. Run rails app:update: This command applies updates to configuration files and other Rails internals. You’ll need to manually review and merge changes.

  4. Fix Deprecations: Resolve any deprecation warnings. Rails provides clear error messages to help you identify outdated methods or configurations.

Note on rails app:update

This command updates your app's configuration files but does not provide insights into compatibility or potential issues. Manual reviews are crucial after running this command.


6. Test Thoroughly

Testing is non-negotiable during a Rails upgrade.

A comprehensive testing process helps you identify and resolve issues early.

Steps to Ensure a Solid Test Process

  • Automated Testing: Run your test suite after each step of the upgrade. Use tools like RSpec, MiniTest, or Capybara for robust coverage.

  • Manual Testing: Focus on critical user flows and edge cases.

  • Performance Testing: Use tools like MiniProfiler or Apache JMeter to verify performance metrics post-upgrade.

Code Example: Comprehensive Upgrade Testing Suite

This comprehensive testing script automates various testing strategies to ensure a robust upgrade process.

# lib/tasks/upgrade_tests.rb
namespace :upgrade do
  desc "Run comprehensive upgrade test suite"
  task full_test_suite: :environment do
    puts "🚀 Starting Comprehensive Upgrade Verification..."

    # Preparation
    Rake::Task['db:migrate'].invoke
    system('bundle install') || raise("Dependency installation failed")

    # Test Suites
    puts "\n🧪 Running Test Suites..."

    # Core Tests
    rspec_result = system('bundle exec rspec')
    minitest_result = system('bundle exec rake test')

    # Model Tests
    models_to_test = [
      'User', 
      'Account', 
      'Product'  # REPLACE WITH YOUR ACTUAL MODELS
    ]

    models_to_test.each do |model|
      puts "Verifying #{model} model integrity..."
      system("bundle exec rspec spec/models/#{model.downcase}_spec.rb")
    end

    # Integration Tests
    system('bundle exec rspec spec/integration') 

    # Performance & Coverage
    system('bundle exec rails performance:test')
    system('bundle exec simplecov')

    # Security Scan
    system('bundle exec brakeman -q')

    # Final Checks
    unless rspec_result && minitest_result
      puts "❌ Some tests failed during upgrade verification!"
      exit 1
    end

    puts "✅ Upgrade Verification Complete!"
  end
end

How to run this testing script?

Ensure you have these gems in your Gemfile:

group :development, :test do
  gem 'rspec-rails'
  gem 'minitest-rails'
  gem 'simplecov'
  gem 'brakeman'
end

Setup Steps

# Install dependencies
bundle install

# Setup test environment
rails generate rspec:install
rails generate minitest:install

Running the Rake Task

# Run full upgrade test suite
rails upgrade:full_test_suite

# Or with more verbose output
VERBOSE=true rails upgrade:full_test_suite

What does this script do?

  • Migrates database

  • Runs RSpec tests

  • Runs Minitest

  • Checks model integrity

  • Performs security scan

  • Generates coverage report

💥 WARNINGS:

  • REPLACE MODEL NAMES with YOUR actual models

  • Ensure you have corresponding spec files

  • This is a TEMPLATE, customize for YOUR app


7. Refactor Code and Leverage New Features

Rails upgrades are an opportunity to modernize your codebase.

Remove outdated patterns and embrace new Rails 8 features.

Examples of Refactoring Opportunities

  • Replace custom implementations with Rails' new built-in solutions (e.g., use Active Storage for file uploads instead of third-party libraries).

  • Remove deprecated APIs and utilize new syntax or patterns introduced in Rails 8.

Code Example: Modern Rails 8 Refactoring

# Modern Active Storage and Hotwire integration
class AttachmentService
  def self.upload_and_process(file)
    attachment = ActiveStorage::Attachment.create!(
      name: 'document',
      record: current_user,
      blob: ActiveStorage::Blob.create_and_upload!(
        io: file,
        filename: file.original_filename,
        content_type: file.content_type
      )
    )

    ProcessDocumentJob.perform_later(attachment.id)
    broadcast_update(attachment)
  end

  private

  def self.broadcast_update(attachment)
    Turbo::StreamsChannel.broadcast_update_to(
      attachment.record,
      target: 'uploads_list',
      partial: 'uploads/status',
      locals: { attachment: attachment }
    )
  end
end

class ProcessDocumentJob < ApplicationJob
  def perform(attachment_id)
    attachment = ActiveStorage::Attachment.find(attachment_id)
    # Perform document processing
  end
end

This example demonstrates leveraging Rails 8 features like Active Storage, background jobs, and Hotwire for modern, efficient code.


8. Deploy and Monitor

After testing, deploy the upgraded application to a staging environment for final checks.

Post-Deployment Checklist

  • Monitor for errors or warnings in logs.

  • Verify that all third-party integrations (e.g., APIs, payment gateways) are functioning correctly.

  • Use Application Performance Monitoring (APM) tools like New Relic or Datadog to track performance in real-time.


9. Educate Your Team

A successful upgrade isn’t just about the code—it’s also about your team. Ensure developers understand the changes introduced in Rails 8.

Training Tips

  • Host workshops to demonstrate new Rails features.

  • Document changes specific to your app and share them with the team.

  • Encourage team members to experiment with and adopt Rails 8 best practices.


Conclusion

Upgrading to Rails 8 in 2025 is a chance to future-proof your application and take advantage of modern tools and practices.

While the process can be complex, careful planning, testing, and incremental progress can minimize risks and ensure a smooth transition.

By following this guide, you’ll be well-equipped to handle the challenges of upgrading while unlocking the benefits of the latest Rails features.

Stay proactive, involve your team, and embrace the upgrade as an opportunity to improve your codebase for the long term.

0
Subscribe to my newsletter

Read articles from Chetan Mittal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chetan Mittal
Chetan Mittal

I stumbled upon Ruby on Rails beta version in 2005 and has been using it since then. I have also trained multiple Rails developers all over the globe. Currently, providing consulting and advising companies on how to upgrade, secure, optimize, monitor, modernize, and scale their Rails apps.