Ruby on Rails 中如何实现 decimal 类型价格的最多两位小数校验
问题原因
- 数据库自动截断精度:如果迁移中给
price字段设置了scale: 2(最多保留两位小数),Rails赋值时会自动对超出位数的小数做四舍五入,比如输入14.995会被自动转为15.0,校验拿到的已经是处理后的值,无法触发校验失败。 - 类型不匹配:正则校验针对字符串生效,而
price作为decimal类型实际是BigDecimal对象,大数值转成字符串时会以科学计数法表示,正则无法匹配这类格式,导致校验失效。
解决方案
前置调整
先修改数据库迁移,将price的小数位数上限放宽到大于2(比如5位),避免赋值时被自动四舍五入,让校验可以拿到原始输入的精度:
# db/migrate/xxxxxx_create_logos.rb t.decimal :price, precision: 10, scale: 5
模型层自定义校验
通过「乘以100后是否为整数」的逻辑判断小数位数,避免正则匹配和浮点数精度问题:
class Logo < ApplicationRecord validates :name, presence: true, length: { maximum: 255 } validates :price, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :stitch_count, presence: true, numericality: { greater_than: 0, only_integer: true } validate :price_must_have_at_most_two_decimal_places private def price_must_have_at_most_two_decimal_places return if price.nil? # 统一转成BigDecimal处理,避免浮点数精度误差 decimal_price = BigDecimal(price.to_s) # 乘以100后若存在小数部分,说明原始值超过两位小数 unless (decimal_price * 100).modulo(1).zero? errors.add(:price, "最多只能保留两位小数") end end end
校验效果
- 合规值
10.44、12.1、16、14.00可正常通过 - 非合规值
10.123、13.11111、14.995会触发校验错误
测试适配
原有测试用例无需修改,完成上述调整后即可正常运行:
def setup @logo = Logo.new(name: "ASG Security", price: 12, stitch_count: 15000, note: "Ali") end test "price scale must be less than 2" do @logo.price = 14.995 assert_not @logo.valid? end
内容的提问来源于stack exchange,提问作者izaguirrejoe
相关产品推荐
相关产品推荐

