升级 Ruby 到 3.2
Upgrade Ruby from 3.0 to 3.2
眼看着 Ruby 3.3 的 preview 2 版本都已经发出来了, 于是我就着手把一个 3.0 的项目升级到 3.2了。可能是我这样直接从 3.0 到 3.2 升级跨度点大,还是遇到了一些不大不小的问题,我在这里做一个整理,希望可以帮助到大家。
Bundled gems
在 Ruby 3.1 中,有些 lib 从标准库变成了 bundled gems,比如我项目中用到的 net-ftp 还有 matrix这样的库。具体的列表可以在 Ruby 3.1.0 Released 看到,滚动到 “Standard libraries updates” 这一部分就可以看到。
这意味我们如果使用了 bundler,那就需要在 Gemfile 中把 matrix 等gem加进去,否则在运行的过程中会遇到这样的问题:
Rails `require': cannot load such file -- matrix
顺便再说一些 bundled gems 和 default libraries 的区别。
以 matrix 为例,在 Ruby 3.1 中,matrix 已从 Ruby 标准库的一部分转变为bundled gem,这一变化并不意味着它从 Ruby 的默认库中移除,而是意味着 matrix 现在被视为 gem。
bundled gems:这些 lib 现在被视为 gem,当使用 bundler创建新的 Ruby 项目时,我们需要在 Gemfile 中包含这些 gem。但是作为 "bundled" gem,我们不需要单独安转它们,他们
default libraries:在安装 Ruby 时,标准库中仍提供这些库,即在使用纯 Ruby 时,我们依然可以访问这些库。
Psych (YAML)的安全性更新
YAML 的 load
和 load_file
方法的行为有一些 breaking changes,主要也是底层的 Psych 库的改变。
过去能正常运行的 YAML.load
可能现在回出现这样的错误:
YAML.load "--- !ruby/object:Matrix\nrows:\n- - 25\n - 93\n- - -1\n - 66\ncolumn_count: 2\n"
Psych::DisallowedClass:
Tried to load unspecified class: Matrix
这是因为 YAML.load
现在会调用 Psych.safe_load
, 在解析 yaml 的时候,会限制 Ruby 对象的种类,以保证其安全性。
解决办法如下:
如果你信任你的数据,那么可以用
YAML.unsafe_load
去替换YAML.load
如果数据的内容不可信,那么需要加上 permited_classes 参数,如下:
YAML.load "--- !ruby/object:Matrix\nrows:\n- - 25\n - 93\n- - -1\n - 66\ncolumn_count: 2\n", permitted_classes: [Matrix]
=> Matrix[[25, 93], [-1, 66]]
在 Rails 项目中,也可以进行在 config/application.rb
进行如下配置:
config.after_initialize do
ActiveRecord.yaml_column_permitted_classes += [Date, Time, ActiveSupport::HashWithIndifferentAccess]
end
这个改动的具体背景可以看这里:[CVE-2022-32224] Possible RCE escalation bug with Serialized Columns in Active Record
Deprecating很久终于移除的方法
File.exists?
这个方法在 Ruby 2.1 中就被标记为 Deprecating,但是也没说具体何时移除。这次在升级到 Ruby 3.2 之后,我遇到了这个错误 undefined method 'exists?' for File:Class
,才反应过来这个方法终于被移除了。一同移除的还有 Dir.exists?
这个方法。具体可以参见 release notes 中 “Removed methods” 这一小节。
但是,在我全局搜索项目,并把 File.exists?
都替换成 File.exist?
后,我还是遇到了类似的错误。因为在我复制粘贴来的 chef 脚本里面,还有 FileTest.exists?
这种用法。
我随后查了下,File.exist?
或者 File.file?
这样的测试方法,都是定义在 FileTest
这个 module里的,而 File 这个类 include 了 FileTest module,而 Ruby 也允许我们直接用 FileTest 去调用这些方法。所以,对于 FileTest.exists?
这样的用法,也需要改成 FileTest.exist?
。