探码开发文档
  • 探码科技-发开日志
  • 代码规范
  • 前端
    • Bootstrap 模板
      • 图表类
      • Profile 类页面
    • JS Chart图表
    • 图片库
    • Icon图标库
    • Css3
      • 字体+背景混合搭配
      • tranform-origin + transform
      • flex
        • 布局左边自适应,右边固定宽度
    • 用Sass颜色函数控制颜色
    • Draggable组件库
  • Javascript
  • Ruby
    • Ruby on Rails
      • 数据库类
      • 日志类
      • 价格字段的单位转换
      • 部署
      • 根据设备类型自动渲染页面
      • 路由
    • Gems
    • Automating your API with JSON Schema
    • 深度冻结变量 Deep Freeze
    • 搭建私有Gem仓库
    • YAML语法
  • 数据库
    • PostgreSQL
      • 基础知识
        • PostgreSQL中插入数据
        • PostgreSQL中更新数据
        • PostgreSQL中删除数据
      • 数据库管理
      • select jsonb
    • PostgreSQL XL
      • Data Definition
      • 查询技巧
  • Docker
    • Docker日志收集最佳实践
    • Harbor搭建私有镜像服务
  • Kubernetes
    • 参考资源
    • Kubeadm方式安装Kubernetes
    • Rancher方式安装Kubernetes
      • RBAC集成
    • rke方式安装Kubernetes
    • RBAC用户管理
    • Traefik配置
    • 创建etcd集群
    • Kubeapps
    • 工具
    • 安装Helm
    • 亲和度配置
  • 文件系统
    • GlusterFS
  • 日志管理
    • Fluentd
  • VirtualBox
  • 工具软件
    • Alfred
    • 代码版本控制工具
    • Atom
    • Bash Shell
    • Vim
    • fzf(Fuzzy Finder)
    • Gitlab
  • Ubuntu
    • 安装 VPN服务
    • 安装DNSMasq
    • Keepalived
    • OpenSSL 使用技巧
  • Git
  • Nginx
    • 自动更新SSL证书
    • 使用stream模块实现负载均衡
  • 机器学习
Powered by GitBook
On this page
  1. Ruby
  2. Ruby on Rails

价格字段的单位转换

我们在存储一个产品的价格时,字段类型的设计可以有以下几种:

  • Decimal - 以元为单位存储浮点数

  • Integer - 以分为单位储存整数

如果用浮点数存储数据,那么我们在表单中录入价格时,时常会遇到一个奇怪的现象,录入的价格是912.00元,而存储数据库时莫名其妙地变成了911.999999999999 。呈现到前端时,就很怪异了。

在ruby中我可以通过下面测试,发现同样问题:

2.3.4 :017 > "9.12".to_f * 100
 => 911.9999999999999

因此,我们就有了以分为单位存储的想法。但是,以分为单位,我们在生成表单时,又想让用户以元为单位录入价格,我们可能会这么做:

rails g scaffold product name price_cents:integer
app/models/product.rb
class Product < ApplicationRecord
    # 数据库存储价格字段是 price_cents,整形,单位为分
    def price
        (price_cents || 0) / 100.0
    end

    def price=(v)
        # 这里用.round就是为了解决上面的911.99999999问题
        self.price_cents = (v.to_f * 100).round
    end
end
app/controllers/products_controller.rb
def product_params
    params.require(:product).permit(:name, :price)
end
app/views/products/_form.html.erb
  <div class="field">
    <%= form.label :price %>
    <%= form.text_field :price, id: :product_price %>
  </div>

封装一下代码,做到足够DRY

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    def price_attr(model_attr, db_attr="#{model_attr}_cents")
      class_eval <<-CODE, __FILE__, __LINE__ + 1
        def #{model_attr}
          (#{db_attr} || 0) / 100.0
        end

        def #{model_attr}=(v)
          self.#{db_attr} = (v.to_f * 100).round
        end
      CODE
    end
  end
end
app/models/product.rb
class Product < ApplicationRecord
    price_attr :price
end
require 'money'

# 10.00 USD
money = Money.new(1000, "USD")
money.cents     #=> 1000
money.currency  #=> Currency.new("USD")

# Comparisons
Money.new(1000, "USD") == Money.new(1000, "USD")   #=> true
Money.new(1000, "USD") == Money.new(100, "USD")    #=> false
Money.new(1000, "USD") == Money.new(1000, "EUR")   #=> false
Money.new(1000, "USD") != Money.new(1000, "EUR")   #=> true

# Arithmetic
Money.new(1000, "USD") + Money.new(500, "USD") == Money.new(1500, "USD")
Money.new(1000, "USD") - Money.new(200, "USD") == Money.new(800, "USD")
Money.new(1000, "USD") / 5                     == Money.new(200, "USD")
Money.new(1000, "USD") * 5                     == Money.new(5000, "USD")

# Unit to subunit conversions
Money.from_amount(5, "USD") == Money.new(500, "USD")  # 5 USD
Money.from_amount(5, "JPY") == Money.new(5, "JPY")    # 5 JPY
Money.from_amount(5, "TND") == Money.new(5000, "TND") # 5 TND

# Currency conversions
some_code_to_setup_exchange_rates
Money.new(1000, "USD").exchange_to("EUR") == Money.new(some_value, "EUR")

# Formatting (see Formatting section for more options)
Money.new(100, "USD").format #=> "$1.00"
Money.new(100, "GBP").format #=> "£1.00"
Money.new(100, "EUR").format #=> "€1.00"

Previous日志类Next部署

Last updated 7 years ago

以上代码,足够应付我们的价格前后端单位转换问题。如果需要更高级的功能,我们可以尝试Gem 或

money
money-rails