为什么 singleton_class 个对象在 Ruby 2.4 而不是 2.3 中冻结?

Why are singleton_class objects frozen in Ruby 2.4 but not in 2.3?

我们有一个应用程序使用 Sequel gem 连接到数据源,执行一些工作,然后 return 结果附有许多方便的方法到该对象的 singleton_class。在 ruby 2.3 中,此代码按预期工作:

result = EpulseDB::Employee.where(normalized_args)
result.singleton_class.include(EpulseNormalization)

我们可以看到使用 ruby 2.3.4 singleton_class 没有被冻结:

[1] pry(main)> result = EpulseDB::Employee.where(employee_id: 2)
=> #<Sequel::Postgres::Dataset: "SELECT * FROM \"employee\" WHERE (\"employee_id\" = 2)">
[2] pry(main)> result.frozen?
=> true
[3] pry(main)> result.singleton_class.frozen?
=> false
[4] pry(main)> result.singleton_class.include(EpulseNormalization)
=> #<Class:#<Sequel::Postgres::Dataset:0x007feff0903660>>

但在 Ruby 2.4.2 中,singleton_class 似乎被 return 冻结,我们无法再扩展它。有没有我应该使用的扩展单例的新方法??

[1] pry(main)> result = EpulseDB::Employee.where(employee_id: 2)
=> #<Sequel::Postgres::Dataset: "SELECT * FROM \"employee\" WHERE (\"employee_id\" = 2)">
[2] pry(main)> result.frozen?
=> true
[3] pry(main)> result.singleton_class.frozen?
=> true
[4] pry(main)> result.singleton_class.include(EpulseNormalization)
RuntimeError: can't modify frozen object
from (pry):4:in `append_features'

使用 Dataset#with_extend 来 return 使用模块扩展的数据集的修改副本,而不是调用 Dataset#extend 来修改数据集本身。这适用于 Sequel 支持的所有 ruby 版本。

背景故事:这与 Ruby 本身无关,这是由于 Sequel 中缺少 Ruby <2.4.

功能的解决方法

在Ruby <2.4中,Object#freeze无法处理Object#clone用于创建冻结对象的修改副本(包括对象的单例副本class). Ruby 2.4 将 freeze: false 选项添加到 Object#clone 以允许创建冻结对象的修改副本,包括它们的单例 class(参见 https://bugs.ruby-lang.org/issues/12300)。

Sequel::Dataset 在内部使用 #clone 到 return 修改后的数据集,并且要求数据集包含任何用于正常运行的单例 classes 的副本。因为我希望 Sequel::Dataset 被冻结,但仍然在 ruby < 2.4 上工作,所以它基本上假装在 ruby <2.4 上被冻结。它仅在 ruby 2.4 中才真正冻结。参见: