We have made all versions of Rails LTS compatible with Ruby 3.3 or below. All Rails components should work as expected with no deprecation warnings.
However, upgrading Ruby will require manual effort. Your application may contain code that does not work on the latest Ruby. It is likely that some of your third-party dependencies do not work on the latest Ruby. The upgrading steps vary for every Rails application, and increase with the number of third-party gems.
We have ourselves successfully upgraded several older applications. Usually, the required changes to the application code are minimal and in most cases it was possible to find compatible versions of all gems. In rare cases however, we did need to use monkey patches or fork a gem.
Upgrading Ruby should still be significantly easier than doing a full Rails upgrade.
Why you would upgrade
There are a few reasons to use a newer Ruby version:
- Ruby versions are only under active maintenance for roughly 3 years after release. Older Rubies do have a few known security issues such as buffer overreads. While these vulnerabilities are usually difficult to abuse in the context of a Rails application, they do exist.
- Newer Rubies are faster. This is most pronounced when you upgrade from Ruby 1.8 (expect a 2x - 3x performance boost) or early 2.x versions.
- It might be difficult to install older Ruby versions on modern operating systems. This is mostly an issue for Ruby < 2.5.
Compatibility matrix
Rails LTS is tested only against a subset of Ruby versions (decreasing the maintenance effort on our part).
We recommend to use one of these combinations:
| Rails 2.3 LTS | Rails 3.2 LTS | Rails 4.2 LTS | Rails 5.2 LTS | Rails 6.1 LTS | |
|---|---|---|---|---|---|
| Ruby 1.8.7 | ✅ | ✅ | |||
| Ruby 1.9.3 | ✅ | ||||
| Ruby 2.1.x | ✅ | ||||
| Ruby 2.2.x | ✅ | ||||
| Ruby 2.3.x | ✅ | ✅ | ✅ | ✅ | |
| Ruby 2.5.x | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ruby 2.7.x | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ruby 3.1.x | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ruby 3.3.x | ✅ | ✅ | ✅ | ✅ | ✅ |
How to upgrade Ruby
If you decide to upgrade Ruby across major versions, we recommend doing the upgrade in steps.
- When starting from Ruby 1.8.7, upgrade to 2.5 first
- When upgrading to Ruby 3.x, upgrade to 2.7 first
During each upgrade round, follow this workflow:
- Update to the latest patch level of your Rails LTS version.
- Update your Ruby version.
- Follow the tips and instructions in this card for your LTS version, gems, and Ruby version.
- Run
bundle install. - Try to get
rails console(orscript/console) running without errors. - Try to get
rails server(orscript/server) running without errors. - Run your tests and fix remaining errors.
Notes for Rails 2.3 LTS
- If you haven't already, you need to switch to Bundler.
- Add
gem 'test-unit', '= 1.2.3', require: falseto your Gemfile as a top-level gem (not inside thegroup :test). - In
config/preinitializer.rb, add:Encoding.default_external = Encoding::UTF_8 - Check
script/server,script/consoleetc. The firstrequireline should look like this:require File.expand_path('../../config/boot', __FILE__)
Notes about gems
If you see errors produced by third party gems (usually recognizable in the stacktrace), we recommend going to rubygems.org, searching for the gem and looking for the latest gem version that supports your Rails major version (e.g. if you are on Rails 3, find the latest gem that does not require activerecord ~> 4 etc.).
We have some explicit notes for a few common gems:
mysql / mysql2
If you're using mysql, please switch to the mysql2 gem. To do this, add it to your Gemfile, and set your database adapter to mysql2 in your database.yml.
mysql2 is mostly a drop-in replacement for mysql. The main difference is that you might sometimes get casted values (i.e. Time objects instead of strings) when you use low-level methods in ActiveRecord, such as select_values.
The following mysql2 version are known to work:
- 0.5.4 on Ruby 2.5
- 0.5.6 (latest at time of writing) on all newer Rubies
Note that we used to offer a fork for mysql2. This is no longer recommended, and the 0.5.x versions of mysql2 do work even on the latest Rails 2.3 LTS.
pg
All versions of Rails LTS and all Rubies >= 2.5 should work with pg version 1.5.6 (latest at time of writing).
i18n
When upgrading to Ruby 3+, please upgrade i18n to at least version 1.9.
rake
When upgrading to Ruby 3.3+, please upgrade rake to at least version 13.
rspec 1.x
If your application still uses a 1.x version of rspec, we recommend upgrading to
our fork of RSpec 1
Show archive.org snapshot
like this:
gem "rspec", git: 'https://github.com/makandra/rspec.git', branch: '1-3-lts'
gem "rspec-rails", git: 'https://github.com/makandra/rspec-rails.git', branch: '1-3-lts'
therubyracer
We recommend to replace this with the mini_racer gem. You may also have to update the execjs gem to allow this.
date-performance
This gem is no longer required, please remove it.
fastercsv
Please drop this gem. Instead, require 'csv', which has the same API.
Upgrading from Ruby 1.x to Ruby 2+
YAML parser changes
The YAML parser has changed from Syck to Psych. Some .yaml files need to be fixed for Psych. One common case is found in default locale files. Instead of
order: [:day, :month, :year]
use
order:
- :day
- :month
- :year
You also now need to quote strings that start with * or &.
Finally, if you made use of ActiveRecord's serialize feature, you might want to check that serialized data in your database can still be loaded.
Lambdas
lambdas (but not procs) have started to enforce their arity. You can no longer call a lambda with arguments if the block does not take any.
Fix this by switching the offending (or possibly all) lambdas to procs.
Changes to Ruby's standard library
-
object.respond_to?(:a_protected_method)used to betrue, but is nowfalse. You can useobject.respond_to?(:a_protected_method, true)instead (which will also be true for private methods). -
object.idno longer aliasesobject.object_id -
Array("line 1\nline 2")no longer splits on linebreaks. Use"line 1\nline 2".linesinstead if you usedArrayfor that purpose. -
Array#to_sused to work likeArray#join, but now works likeArray#inspect. UseArray#joinexplicitly. -
"some words".eachis gone. Use"some words".split.each. - A few methods no longer accept symbols instead of strings (
"foo".starts_with?(:f)is now an error). -
iconvis no longer in the standard library. You can add it as a gem, or better replace it withString#force_encoding/String#encode:# old converter = Iconv.new('UTF-8//IGNORE', 'WINDOWS-1252') converter.iconv(text) # new text.force_encoding('WINDOWS-1252').encode('UTF-8', undef: :replace, invalid: :replace)
String encoding
You can potentially run into issues with String encoding. In general, everything should always be encoded as UTF-8. If you deal with binary data or lowlevel operations (like String#unpack), you must potentially use String#force_encoding or String#encode.
Upgrading from Ruby < 2.5 to Ruby 2.5+
Ruby 2.4+ deprecates Fixnum and Bignum in favor of a unified Integer class. This "only" causes deprecation errors, since Fixnum internally resolves to Integer.
If a third-party gem uses Fixnum and you want to resolve the deprecation errors, you can often manually define a Fixnum class in the gem's namespace. For example, we've added the following initializer to one of our projects:
# config/initializer/fixnum_deprecation_fixes.rb
# will_paginate
WillPaginate::Fixnum = Integer
BootstrapPagination::Fixnum = Integer
# axlsx
Axlsx::Fixnum = Integer
Upgrading from Ruby 2.x to Ruby 3
Ruby 3 introduces many breaking changes, mainly by changing the APIs of some standard library methods and by changing how keyword arguments work.
The ruby-3-backward-compatibility gem
To address some issues in Ruby 3, we created the ruby-3-backward-compatibility gem Show archive.org snapshot . Please add it to the top of your Gemfile (Rails LTS will require it automatically).
It mostly adds back some removed APIs in Ruby. For example:
# old API
ERB.new(template, nil, '-', '@output_buffer')
# new API
ERB.new(template, trim_mode: '-', eoutvar: '@output_buffer')
# old API
YAML.safe_load(yaml, [Time], [], true, 'foo.yml')
# old API
YAML.safe_load(yaml, permitted_classes: [Time], permitted_symbols: [], aliases: true, filename: 'foo.yml')
The ruby3-backward-compatibility gem patches all of these, so you should not run into problems and do not have to change your code.
YAML safety
As you probably know, loading user-controlled YAML is dangerous. Exploits work by serializing internal Ruby objects to YAML, which can cause code execution when getting de-serialized.
This is why YAML introduced YAML.safe_load. It used to work like this:
- Calling
YAML.safe_loadon dangerous YAML content would produce errors. - Calling
YAML.loadorYAML.unsafe_loadwould produce no errors and must not be called with unsanitized user input.
When upgrading to psych version >= 4 (the "yaml" gem; which is default for Ruby 3), this behavior changes as follows:
- Calling
YAML.loadorYAML.safe_loadon dangerous YAML content will produce errors. - Calling
YAML.unsafe_loadproduces no errors. You must not call it with (unsanitized) user input.
Any code (including third-party gems) that is unaware of this change and uses YAML.load instead of YAML.unsafe_load will start producing errors. For example, i18n versions < 1.9 will crash when loading your locale files.
This is something we cannot address globally, as there is no way of knowing which instances of YAML.load are fine to load dangerous content, and which are not.
Hence, you need to manually change YAML.load to YAML.unsafe_load where appropriate.
Keyword arguments
Ruby 3 no longer allows to call methods expecting keyword arguments with a hash. Example:
def method_with_kwargs(param, keyword_param: 'default'); end
# no longer works
method_with_kwargs('foo', { keyword_param: 'bar' })
# works as before
method_with_kwargs('foo', keyword_param: 'bar')
method_with_kwargs('foo', :keyword_param => 'bar')
method_with_kwargs('foo', **{ keyword_param: 'bar' })
# the inverse (converting keyword to an options hash) still works fine
def method_with_options_hash(params, options = {}); end
method_with_options_hash('foo', bar: 'baz')
Unfortunately, the invalid invocations happen all over the place. To simplify the fix, the ruby3-backward-compatibility gem introduces a callable_with_hash helper:
def method_with_kwargs(param, keyword_param: 'default'); end
extend Ruby3BackwardCompatibility::CallableWithHash
callable_with_hash :method_with_kwargs
# now this works again
method_with_kwargs('foo', { keyword_param: 'default' })
callable_with_hash works by wrapping the original method with a version that accepts both an options hash or keyword arguments.
You can even use this on external APIs:
# i18n now requires keyword args, so this fails
I18n.t('user', { scope: ['models'] })
# This makes it work again (this is already included in the ruby3-backward-compatibility gem)
module I18n
extend Ruby3BackwardCompatibility::CallableWithHash
callable_with_hash :t
end
The ruby3-backward-compatibility gem applies callable_with_hash to a significant number of Rails methods, so you should mostly be good calling Rails methods with either style.
Delegation
If your code uses some custom delegation (or method_missing) style code, you will have to take care of keyword arguments as well.
class Foo < Base
# this does not work with keyword args
def method1(*args)
super
end
# this will
def method2(*args, **kwargs)
super
end
# as will this
def method3(...)
super
end
end
If for some reason you have to write code that also works with some legacy ruby versions, find even more details here.
Upgrading to Ruby 3.3+
ruby3-backward-compatibility gem
Please update to the latest version of the ruby3-backward-compatibility gem.
mysql2
In case you are still using our mysql2 fork, please upgrade to the latest official release (see above).
Default gems
Ruby 3.3 will print a lot of warnings about gems being removed from the list of default gems (such as csv, bigdecimal etc.).
If you see such a warning, just add the gem to your Gemfile.