Upgrade rails from 5.2 to 6.0

The main things to handle during upgrading API centric application's rails version from 5.2 to 6.0 include configuration tuning and deprecation warnings fixing.

Configurations

  • The dalli_store is deprecated and can be replaced with rails built-in mem_cache_store
# Change to this in all environment based config files
config.cache_store = :mem_cache_store
  • Set defaults to load 6.0 by default but flip some default values to maintain backward compatibility (case by case), for example in application.rb file:
config.load_defaults 6.0

# Rails 6 introduces a new autoloader called :zeitwerk, which will cause autoloading problems if any
# of the classes/modules didn't conform to namespace naming conventions and :classic autoloading can
# still be used until upgrading to rails v7.0+ by setting the autoloader to :classic as below example
config.autoloader = :classic
  • When overriding default config, be careful of config not taking effect due to wrong execution order of initializers messed by 3rd-party gems for example:
# NOTE: To override defaults, move the defaults from config/initializers/new_framework_defaults_x_x.rb
# here due to some weird issue causing the item from config/initializers/new_framework_defaults_x_x.rb
# not to take effect because of wrong execution order, see: https://github.com/rails/rails/issues/45105

# Override defaults from rails 5.2
# https://guides.rubyonrails.org/configuring.html#default-values-for-target-version-5-2
# By default set to true, this would add "protect_from_forgery: :exception" that breaks APIs endpoints
config.action_controller.default_protect_from_forgery = false
# ...

# Override defaults from rails 6.0:
# https://guides.rubyonrails.org/configuring.html#default-values-for-target-version-6-0
# By default set to true, this would make cookies incompatible between rails 5.x and 6.x versions
# See https://guides.rubyonrails.org/v6.0/upgrading_ruby_on_rails.html#purpose-in-signed-or-encrypted-cookie-is-now-embedded-within-cookies
config.action_dispatch.use_cookies_with_metadata = false
# ...

By setting config.action_controller.default_protect_from_forgery = false, it addresses an error Can't verify CSRF token authenticity raised due to API requests not having CSRF token. However, if we do want protection from rails and just want to skip for some endpoints, there are other options such as:

# add either of below filters to the controller
skip_forgery_protection
skip_before_action :verify_authenticity_token # equivalent to the above as it's an alias

# limit exception strategy on specific actions only
protect_from_forgery with: :exception, only: :create

# totally disable protection (not recommended)
config.action_controller.allow_forgery_protection = false


# use null session to handle requests without CSRF tokens
protect_from_forgery
protect_from_forgery with: :null_session # equivalent to the above as it's the default option

Deprecation warnings

DEPRECATION WARNING: update_attributes is deprecated and will be removed from Rails 6.1 (please, use update instead)

Solution: replace update_attributes with update and update_attributes! with updates! in models and controllers where they are used.

DEPRECATION WARNING: Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.

Solution: add { case_sensitive: true } or { case_sensitive: false } to align model uniqueness validation with db schema definitions.

DEPRECATION WARNING: `Module#parent` has been renamed to `module_parent`. `parent` is deprecated and will be removed in Rails 6.1.

Solution: follow this post to identify what is causing the warning messages. If it's caused by outdated gems, update to the version with fix. For example, in the active_model_serializers gem, its 0.9.7 version has the warnings but the 0.9.8 version has the fix as the diff shows.

DEPRECATION WARNING: ActionView::Base instances should be constructed with a lookup context, assignments, and a controller.
DEPRECATION WARNING: render file: should be given the absolute path to a file
DEPRECATION WARNING: ActionView::Base instances must implement `compiled_method_container` or use the class method `with_empty_template_cache` for constructing an ActionView::Base instance that has an empty cache.

Solution: update to use new way to render by ActiveView::Base outside controller, for example:

def custom_render(opts)
  lookup_context = ActionView::LookupContext.new(ActionController::Base.view_paths)
  lookup_context.cache = false
  context = ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil)
  context.class_eval do
    include ApplicationHelper
  end
  renderer = ActionView::Renderer.new(lookup_context)
  renderer.render(context, opts)
end

References

rails ruby