Ruby on rails 使用Ruby on Rails从数据库中的yaml序列化字段返回大小数
使用RubyonRails,我有几个字段是序列化的(主要是数组或散列)。其中一些包含Ruby on rails 使用Ruby on Rails从数据库中的yaml序列化字段返回大小数,ruby-on-rails,serialization,bigdecimal,Ruby On Rails,Serialization,Bigdecimal,使用RubyonRails,我有几个字段是序列化的(主要是数组或散列)。其中一些包含BigDecimals。这些大小数保持为大小数非常重要,但Rails正在将它们变成浮点数。如何取回BigDecimals 研究这个问题时,我发现在无Rails的纯Ruby中序列化一个大的十进制数,效果与预期的一样: BigDecimal.new("42.42").to_yaml => "--- !ruby/object:BigDecimal 18:0.4242E2\n...\n" 但在Rails控制台中
BigDecimal
s。这些大小数保持为大小数非常重要,但Rails正在将它们变成浮点数。如何取回BigDecimal
s
研究这个问题时,我发现在无Rails的纯Ruby中序列化一个大的十进制数,效果与预期的一样:
BigDecimal.new("42.42").to_yaml
=> "--- !ruby/object:BigDecimal 18:0.4242E2\n...\n"
但在Rails控制台中,它不会:
BigDecimal.new("42.42").to_yaml
=> "--- 42.42\n"
这个数字是大小数的字符串表示形式,所以没关系。但当我读回它时,它被读取为一个浮点,所以即使我将它转换为BigDecimal
(这是我不想做的,因为它容易出错),我也可能会失去精度,这对我的应用程序来说是不可接受的
我在activesupport-3.2.11/lib/active\u support/core\u ext/big\u decimal/conversions.rb
中找到了罪魁祸首,它覆盖了BigDecimal中的以下方法:
YAML_TAG = 'tag:yaml.org,2002:float'
YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
# This emits the number without any scientific notation.
# This is better than self.to_f.to_s since it doesn't lose precision.
#
# Note that reconstituting YAML floats to native floats may lose precision.
def to_yaml(opts = {})
return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
YAML.quick_emit(nil, opts) do |out|
string = to_s
out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
end
end
他们为什么要这样做?更重要的是,我该如何解决这个问题?您提到的ActiveSupport核心扩展代码在主分支中“已经”修复(大约有一年的历史,并且撤销了一个旧的实现),但是由于Rails 3.2只获得安全更新,您的应用程序可能会被旧的实现卡住 我想你有三个选择:
BigDecimal#to_yaml
实现(猴子补丁-猴子补丁)向后移植BigDecimal#to_yaml
的原始实现,但在某种程度上导致了错误。我想我会让您(或其他StackOverflow用户)来决定如何对特定方法进行后端口
作为快速而肮脏的解决方法,您可以简单地使用Syck作为YAML引擎。在同一个问题中,使用这段代码(您可以将其放在初始值设定项文件中):
YAML::ENGINE.yamler='syck'
类BigDecimal
def to_yaml(opts={})
YAML::快速发射(对象id,选项)执行|
标量(“标签:induktiv.at,2007:BigDecimal”,self.to_s)
结束
结束
结束
YAML.add_domain_type(“induktiv.at,2007”,“BigDecimal”)do|type,val|
BigDecimal.new(val)
结束
这里的主要缺点(除了Ruby 2.0.0上的Syck不可用之外)是,您无法在Rails上下文中读取正常的BigDecimal转储,每个想要读取YAML转储的人都需要相同类型的加载程序:
BigDecimal.new('43.21')。至
#=>“--!induktiv.at,2007/BigDecimal 43.21\n”
(将标记更改为“tag:ruby/object:BigDecimal”
也不会有帮助,因为它会产生!ruby/object/BigDecimal
。)
更新–我迄今学到的东西
config/application.rb
没有帮助:
需要文件。展开路径('../boot',文件)
#(a)
%w[yaml psych bigdecimal]。每个{lib | require lib}
类BigDecimal
#备份旧方法定义
@@old_to_yaml=实例方法:to_yaml
@@old_to_s=实例_方法:to_s
结束
需要“rails/all”
#(b)
类BigDecimal
#恢复旧的行为
define_method:to_yaml do | opts={}|
@@旧到旧绑定(自身)。(选项)
结束
定义_方法:to_s do | format='E'|
@@旧到旧绑定(self)。(格式)
结束
结束
#(c)
在不同的点(这里是a、b和c),aBigDecimal.new(“42.21”)。to_yaml
产生了一些有趣的输出:
#(a)=>“--!ruby/object:BigDecimal 18:0.4221E2\n…”
#(b)=>“--42.21\n…”
#(c)=>“--0.4221E2\n…”
其中a是默认行为,b是由ActiveSupport核心扩展引起的,c应该是与a相同的结果。也许我错过了什么class-Person
如果您使用的是Rails 4.0或更高版本(但低于4.2),您可以通过使用删除方法
BigDecimal\encode\u来解决这个问题
您可以使用undef\u方法
将其存档:
require 'bigdecimal'
require 'active_support/core_ext/big_decimal'
class BigDecimal
undef_method :encode_with
end
我把这段代码放在一个初始值设定项中,现在它可以工作了。
Rails猴子补丁的“还原”在Rails 4.2中是不必要的,因为删除了猴子补丁。对于Rails 3.2,以下工作:
# config/initializers/backport_yaml_bigdecimal.rb
require "bigdecimal"
require "active_support/core_ext/big_decimal"
class BigDecimal
remove_method :encode_with
remove_method :to_yaml
end
如果没有此修补程序,在rails 3.2控制台中:
irb> "0.3".to_d.to_yaml
=> "--- 0.3\n...\n"
使用此修补程序:
irb> "0.3".to_d.to_yaml
=> "--- !ruby/object:BigDecimal 18:0.3E0\n...\n"
您可能希望将其包装在带有文档和弃用警告的版本测试中,例如:
# BigDecimals should be correctly tagged and encoded in YAML as ruby objects
# instead of being cast to/from floating point representation which may lose
# precision.
#
# This is already upstream in Rails 4.2, so this is a backport for now.
#
# See http://stackoverflow.com/questions/16031850/getting-big-decimals-back-from-a-yaml-serialized-field-in-the-database-with-ruby
#
# Without this patch:
#
# irb> "0.3".to_d.to_yaml
# => "--- 0.3\n...\n"
#
# With this patch:
#
# irb> "0.3".to_d.to_yaml
# => "--- !ruby/object:BigDecimal 18:0.3E0\n...\n"
#
if Gem::Version.new(Rails.version) < Gem::Version.new("4.2")
require "bigdecimal"
require "active_support/core_ext/big_decimal"
class BigDecimal
# Rails 4.0.0 removed #to_yaml
# https://github.com/rails/rails/commit/d8ed247c7f11b1ca4756134e145d2ec3bfeb8eaf
if Gem::Version.new(Rails.version) < Gem::Version.new("4")
remove_method :to_yaml
else
ActiveSupport::Deprecation.warn "Hey, you can remove this part of the backport!"
end
# Rails 4.2.0 removed #encode_with
# https://github.com/rails/rails/commit/98ea19925d6db642731741c3b91bd085fac92241
remove_method :encode_with
end
else
ActiveSupport::Deprecation.warn "Hey, you can remove this backport!"
end
#bigdecimal应正确标记,并在YAML中编码为ruby对象
#而不是从可能丢失的浮点表示中转换
#精确性。
#
#这已经在Rails 4.2的上游,所以这是一个