Hegwin.Me

朱雀桥边野草花,乌衣巷口夕阳斜。

升级 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 的 loadload_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 对象的种类,以保证其安全性。

解决办法如下:

  1. 如果你信任你的数据,那么可以用 YAML.unsafe_load 去替换 YAML.load

  2. 如果数据的内容不可信,那么需要加上 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?

< Back