您提到的 ActiveSupport 核心扩展代码“已经”在 master 分支中修复(提交大约有一年的时间,并且撤消了与Rails 2.1.0一样古老的实现),但由于 Rails 3.2 仅获得安全更新,因此您的应用程序可能坚持旧的实现。
我想你会有三个选择:
- 将您的 Rails 应用程序移植到 Rails 4。
- Backport Psych 的
BigDecimal#to_yaml
实现(猴子补丁猴子补丁)。
- 切换到 Syck 作为 YAML 引擎。
每个选项都有其自身的缺点:
移植到 Rails 4在我看来是最好的选择,如果你有时间的话(上面提到的提交自 v4.0.0.beta1 起在 Rails 中可用)。由于它尚未发布,因此您必须使用测试版。我不怀疑会有任何重大变化,尽管一些 GSoC想法读起来好像它们仍然可以进入 4.0 版本......
猴子补丁ActiveSupport 猴子补丁应该相当简单。虽然我没有找到的原始实现BigDecimal#to_yaml
,但一个有点相关的问题导致了这个提交。我想我会留给您(或其他 StackOverflow 用户)如何反向移植该特定方法。
作为快速的解决方法,您可以简单地使用 Syck 作为 YAML 引擎。在同一个问题中,用户rampion 发布了这段代码(您可以将其放在初始化文件中):
YAML::ENGINE.yamler = 'syck'
class BigDecimal
def to_yaml(opts={})
YAML::quick_emit(object_id, opts) do |out|
out.scalar("tag:induktiv.at,2007:BigDecimal", self.to_s)
end
end
end
YAML.add_domain_type("induktiv.at,2007", "BigDecimal") do |type, val|
BigDecimal.new(val)
end
这里的主要缺点(除了 Syck 在 Ruby 2.0.0 上不可用)是,您无法在 Rails 上下文中读取正常的 BigDecimal 转储,并且每个想要读取 YAML 转储的人都需要相同类型的加载器:
BigDecimal.new('43.21').to_yaml
#=> "--- !induktiv.at,2007/BigDecimal 43.21\n"
(将标签更改为"tag:ruby/object:BigDecimal"
也无济于事,因为它会产生!ruby/object/BigDecimal
......)
更新——到目前为止我学到的东西
根据此博客条目,奇怪的行为似乎可以追溯到 Rails 1.2 时代(您也可以说是 2007 年 2 月)。
config/application.rb
以这种方式修改没有帮助:
require File.expand_path('../boot', __FILE__)
# (a)
%w[yaml psych bigdecimal].each {|lib| require lib }
class BigDecimal
# backup old method definitions
@@old_to_yaml = instance_method :to_yaml
@@old_to_s = instance_method :to_s
end
require 'rails/all'
# (b)
class BigDecimal
# restore the old behavior
define_method :to_yaml do |opts={}|
@@old_to_yaml.bind(self).(opts)
end
define_method :to_s do |format='E'|
@@old_to_s.bind(self).(format)
end
end
# (c)
在不同的点(这里是 a、b和c) aBigDecimal.new("42.21").to_yaml
产生了一些有趣的输出:
# (a) => "--- !ruby/object:BigDecimal 18:0.4221E2\n...\n"
# (b) => "--- 42.21\n...\n"
# (c) => "--- 0.4221E2\n...\n"
其中a是默认行为,b是由 ActiveSupport 核心扩展引起的,c应该是与 a 相同的结果。也许我错过了一些东西......
仔细阅读您的问题后,我有一个想法:为什么不以另一种格式序列化,例如 JSON?将另一列添加到您的数据库并随着时间的推移进行迁移,如下所示:
class Person < ActiveRecord::Base
# the old serialized field
serialize :preferences
# the new one. once fully migrated, drop old preferences column
# rename this to preferences and remove the getter/setter methods below
serialize :pref_migration, JSON
def preferences
if pref_migration.blank?
pref_migration = super
save! # maybe don't use bang here
end
pref_migration
end
def preferences=(*data)
pref_migration = *data
end
end