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: false
to 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/console
etc. The firstrequire
line 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
lambda
s (but not proc
s) 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) lambda
s to proc
s.
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.id
no longer aliasesobject.object_id
-
Array("line 1\nline 2")
no longer splits on linebreaks. Use"line 1\nline 2".lines
instead if you usedArray
for that purpose. -
Array#to_s
used to work likeArray#join
, but now works likeArray#inspect
. UseArray#join
explicitly. -
"some words".each
is gone. Use"some words".split.each
. - A few methods no longer accept symbols instead of strings (
"foo".starts_with?(:f)
is now an error). -
iconv
is 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_load
on dangerous YAML content would produce errors. - Calling
YAML.load
orYAML.unsafe_load
would 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.load
orYAML.safe_load
on dangerous YAML content will produce errors. - Calling
YAML.unsafe_load
produces 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.