ruby3-backwards-compatibility
This gem provides a compatibility layer to Ruby 3 projects that still have legacy code written against Ruby 2's stdlib. Only use this if you upgrade to Ruby 3 but still depend on some third-party code that breaks on Ruby 3.
The gem will fix only some (but the most common) incompatibilities.
Installation
Add this line to your application's Gemfile:
gem 'ruby3-backward-compatibility'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install ruby3-backward-compatibility
Usage
You need to require the specific backports yourself, see below.
You can also require all included backports using
require 'ruby3-backward-compatibility/compatibility/all'
Note however that this will not patch anything that has not been required yet. You can always require single patches as shown below.
List of backports
Ruby 3 keyword arguments
One breaking change in Ruby 3 is that methods defined using keyword arguments have to be called with keyword arguments, not with option hashes. For example
class Ruby3Class
def some_method(foo:)
puts foo
end
end
Ruby3Class.new.some_method(foo: 'bar') # works
Ruby3Class.new.some_method({ foo: 'bar' }) # raises an ArgumentError
To fix this for arbitrary methods, you can use the callable_with_hash
method included in this gem, like this:
require 'ruby3_backward_compatibility'
class Ruby3Class
# reopen the class
extend Ruby3BackwardCompatibility::CallableWithHash
callable_with_hash :some_method
end
Ruby3Class.new.some_method(foo: 'bar') # still works
Ruby3Class.new.some_method({ foo: 'bar' }) # now works as well
This will wrap the given method and convert a hash given as the last argument into keywords, similar to how it worked in Ruby 2.
Note: In case the method is also defined in a prepended module, you need to put the extend Ruby3BackwardCompatibility::CallableWithHash
above the prepend
.
Dir
Dir.exists?
has been removed in favor of Dir.exist?
.
To add Dir.exists?
as an alias for Dir.exist?
, use
require 'ruby3_backward_compatibility/compatibility/dir'
ERB
ERB.new
used to have the signature
ERB.new(string, safe_level, trim_mode, eoutvar = '_erbout')
but this was changed to
ERB.new(string, safe_level: nil, trim_mode: nil, eoutvar: '_erbout')
To allow both styles, use
require 'ruby3_backward_compatibility/compatibility/erb'
File
File.exists?
has been removed in favor of File.exist?
.
To add File.exists?
as an alias for File.exist?
, use
require 'ruby3_backward_compatibility/compatibility/file'
Fixnum
Fixnum
has been an alias for Integer
for a while, but was removed.
To add back Fixnum
as an alias for Integer
, use
require 'ruby3_backward_compatibility/compatibility/fixnum'
I18n
I18n
has a few methods (translate
, localize
, etc.) that requires to be called with keywords.
To allow calling it with option hashes, too, use
require 'ruby3_backward_compatibility/compatibility/i18n'
Object
The methods Object#taint
and Object#untaint
were no-ops for a while but started to raise deprecation warnings.
The method Object#=~
always returned nil
if called on anything else than a String. It was removed in Ruby 3.2.
To add back Object#taint
and Object#untaint
as no-ops and Object#=~
on arbitrary receivers, use
require 'ruby3_backward_compatibility/compatibility/object'
Regexp (Ruby 3.2+)
It was possible to instantiate a Regexp with Regexp.new('foo', Regexp::IGNORECASE, 'n')
. The last argument used to indicate the encoding in Ruby 1.8. Up to Ruby 3.2 an argument of "n" or "N" indicated a regexp that ignores encoding (same as the Regexp::NOENCODING flag).
In Ruby 3 this raises an ArgumentError
.
To bring back the old behavior, use
require 'ruby3_backward_compatibility/compatibility/regexp'
YAML (Psych)
Psych version 4 (default for Ruby 3.1) has two changes:
Psych.load
has been renamed toPsych.unsafe_load
- The signature of
Psych.safe_load
has been changed from
Psych.safe_load(yaml, permitted_classes = [], permitted_symbols = [], aliases = false, filename = nil)
to
Psych.safe_load(yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil)
To allow both styles of calling Psych.safe_load
, use
require 'ruby3_backward_compatibility/compatibility/psych'
Attention: There has been a very good reason why Psych renamed the .load
method: You may never use .load
on any external strings. It is possible to create valid YAML strings that lead to the execution of arbitrary code, so calling YAML.load
on user input is a major security vulnerability. For that reason, this library does not change the safe behavior of YAML.load
.
String
String#encode
require keyword arguments. To allow it to be called with an options hash, use
require 'ruby3_backward_compatibility/compatibility/string'
URI
The URI
module allows other libraries to register their own URI schemes. In the past, it was possible to do something like
module URI
@schemes['MYSCHEME'] = MySchemeImplementation
end
In Ruby 3, you have to use
URI.register_scheme 'MYSCHEME', MySchemeImplementation
To add back the old behavior, use
require 'ruby3_backward_compatibility/compatibility/uri'
Things we cannot fix
Proc.new
In Ruby 2, it was possible to use Proc.new
without giving a block. In this case, the Proc took the block from the current context. A common usecase worked like this:
def call_me(block = Proc.new)
block.call
end
block = proc do
puts "called"
end
call_me(block) # works
call_me(&block) # also works
call_me { puts "called" } # also works
This is no longer possible in Ruby 3 and we are not aware of a way to make a generic port for this.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
License
The gem is available as open source under the terms of the MIT License.