Rails 8.1.4 把 decimal 大整数截短,显式选择 Decimal 属性类型
Rails 8.1.4 的无小数位 decimal 转换可截掉大整数字符串后半段。显式 decimal 精度是已确认的局部方案,但属性类型和序列化边界须重新检查。
Rails 从 numeric(20, 0) 字段读写大整数时,字符串明明有二十位,转成属性值后却只剩前十六位,可以检查 Rails 8.1.4 的无小数位 decimal 类型路径。数据库字段容量足够,仍可能在 Ruby 属性转换时已经丢失数字。
这类案例使用 PostgreSQL、Rails 8.1.4 和 Ruby 4.0.7,字段用于保存无符号 64 位整数。一个适合验收的值是:
value = '18446744073709551615'
8.1.4 的 Integer 字符串转换增加了按 _limit * 4 截取输入前缀的保护。BigInteger 虽然取消了普通整数的取值范围限制,却仍继承这段转换;无小数位的 DecimalWithoutScale 又继承 BigInteger。默认 limit 为四时,输入只保留十六个字节,然后才调用 to_i。这与数据库拒绝超范围值不同:SQL 发出前或读出后的类型转换已经改变了内容。
目前这里不宣称已有正式版本修复。针对这一具体字段,已确认的局部方案是显式选用 decimal 转换器并指定精度:
class RecordWithLargeNumber < ApplicationRecord
attribute :my_field, :decimal, precision: 20
end
把 my_field 换成实际属性名,数据库仍保持原来的 numeric(20, 0) 定义。这个声明让属性使用独立的 Decimal 转换路径,避免经过无小数位 decimal 的整数转换分支。它不是去掉所有 Integer 输入保护,也不需要把整个应用的整数类型替换掉。
验收不能只看数据库迁移。先检查赋值,再检查保存和重新加载:
record = RecordWithLargeNumber.new(my_field: value)
raise 'cast lost digits' unless record.my_field.to_i.to_s == value
record.save!
raise 'reload lost digits' unless record.reload.my_field.to_i.to_s == value
这段代码是建议执行的验证,不代表所有数据库或模型组合都已经运行通过。还应检查零、边界值及业务接受的负值,并确认序列化到 API 时保持要求的表示。
显式 decimal 属性通常得到 BigDecimal,而非原来期望的 Integer。依赖对象类型、JSON 表示或整数专有方法的代码需要一并调整。把数字作为浮点数传入还可能先损失精度,应优先从精确字符串或整数输入开始。此局部方案也不自动解决其他无符号整数列或所有字符串转换限制。


