Upgrading an application can be tedious if you don’t have good test coverage.

This article covers the common issues you might face while upgrading the from rails 4 to 5.

If you are upgrading to ruby 2.7 export RUBYOPT="-W0" to avoid getting your screen filled up with deprecations warnings

export RUBYOPT="-W0"

bundle exec rspec

# OR use

RUBYOPT="-W0" bundle exec rspec

Step 1: Upgrading ruby and bundle install

  • Install the required ruby version

    • rvm

      rvm install ruby 2.x.x
      
    • rbenv

      rbenv install 2.x.x
      
  • Change the ruby version in .ruby-version, .rvmrc or Gemfile.

  • Install bundler

    gem install bundler
    
  • Change the rails version

    # Gemfile
    gem 'rails', '4.2.0'
    # To
    gem 'rails', '5.2.4'
    
  • run bundle install

    If you don’t need specific versions of the gems locked it is better to remove the specified version.

    bundle install
    
  • Additional Note: (ruby 2.7)

    Ruby 2.7 has removed these gems which were default in prior versions.

    Add the following gems to your gemfile

    gem 'e2mmap'
    gem 'scanf'
    gem 'thwait'
    

Step 2: Config changes

At this point you should be able to install all the dependencies. But, in order to start the application we need to do some config changes.

  • Remove

    # config/application.rb
    config.active_record.raise_in_transactional_callbacks = true
    
  • Change

    # config/application.rb
    config.serve_static_assets = true
    # or
    config.serve_static_files = true
    
    # to
    config.public_file_server.enabled
    
    # config/application.rb
    config.static_cache_control = 'public, max-age=3600'
    
    #to
    
    config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
    
    # routes.rb
    ProjectName::Application.routes.draw do
    
    # to
    
    Rails.application.routes.draw do
    
  • Create config/initializers/assets.rb

    # config/initializers/assets.rb
    Rails.application.config.assets.version = '1.0'
    
    Rails.application.config.assets.precompile += [
      'application.css', 'application.js'
    ]
    

Step 3: Model & Controller changes

Controllers

  • Replace all *_filter callbacks in favor of *_action

    before_filter
    skip_before_filter
    
    # to
    
    before_action
    skip_before_action
    
    redirect_to(:back)
    
    # to
    
    redirect_back(fallback_location: root_path)
    

Models

  • Models now inherit from ApplicationRecord

    • Create

      # app/models/application_record.rb
      class ApplicationRecord < ActiveRecord::Base
        self.abstract_class = true
      end
      
    • Change the following in all models

      class User < ActiveRecord::Base
      
      # to
      
      class User < ApplicationRecord
      
  • belongs_to is compulsory now.

    Use optional: true if the association is ever going to be nil

    class Post < ActiveRecord::Base
      belongs_to :user, optional: true
      #....
    

    :information_source: In a huge application you might want to keep the old behavior. You can change the settings globally in application.rb

    # config/application.rb
    config.active_record.belongs_to_required_by_default = false
    
    after_destroy :send_confirmation, if: 'user&.notifications_enabled?'
    
    after_destroy :send_confirmation, if: Proc.new { user&.notifications_enabled? }
    
  • attribute_changed? is deprecated in after_save callback

    Deprecated: _changed? doesn't work as expected in after_save & after_update
    Use saved_change_to_?

    # Old
    after_save :send_email, if: :email_changed?
    
    # New
    after_save :send_email, if: -> { saved_change_to_email? }
    

    Deprecated: _was doesn't work as expected in after_save & after_update
    Use _before_last_save

    # Old
    archived_record.assign_attributes(email: email_was)
    
    # New
    archived_record.assign_attributes(email: email_before_last_save)
    
  • AASM if you’re upgrading aasm from 3 -> 4

    Follow this guide - Upgrading AASM from version 3 to 4

Mailers

  • Mailers now inherit from ApplicationMailer

    # app/mailers/application_mailer.rb
    class ApplicationMailer < ActionMailer::Base
      default from: "sample@#{ActionMailer::Base.smtp_settings[:domain]}"
    end
    
    class UserMailer < ActionMailer::Base
    # to
    class UserMailer < ApplicationMailer
    

Step 4: Migration changes

Directly inheriting from ActiveRecord::Migration is not supported.
Please specify the Rails release the migration was written for:

  • Change the following

    class CreateUsers < ActiveRecord::Migration
    
    # to
    
    class CreateUsers < ActiveRecord::Migration[4.2]
    

    :information_source: Easy fix

    grep -rl "ActiveRecord::Migration$" db | xargs sed -i "s/ActiveRecord::Migration/ActiveRecord::Migration[4.2]/g"
    

Step 5: Necessary changes in RSpec

RSpec

  • Upgrading FactoryBot to 5

    DEPRECATION WARNING: Static attributes will be removed in FactoryBot 5.0.
    Please use dynamic attributes instead by wrapping the attribute value in a block:

    FactoryBot.define do
      factory :user do
        name 'John'         # Bad & will throw error
        # To
        name { 'John' }     # Good
    

    :information_source: Easy Fix:

    use rubocop to auto fix the format

    Ref: https://thoughtbot.com/blog/deprecating-static-attributes-in-factory_bot-4-11

    rubocop -a spec/factories/xyz.rb --only FactoryBot/AttributeDefinedStatically
    

    Make sure to comment the following in rubocop.yml temporarily

    AllCops:
      Exclude:
        - 'spec/**/*'
    
  • Fix controller specs

    get :show, id: request.id
    xhr :get, :summary, id: request.id
    
    # to
    
    get :show, params: { id: request.id }
    get :summary, params: { id: request.id }, xhr: true
    

    :information_source: This gem does it for you - rails5-spec-converter

Additional Issues you might get while running RSpec

  • undefined method ‘sort’ for #<ActionController::Parameters:>

    Do not use Hash methods on params