关于ruby on rails:处理货币/货币的最佳方法是什么?

What is the best method of handling currency/money?

我正在研究一个非常基本的购物车系统。

我有一个表items,它有一个列price,类型为integer

我无法在视图中显示包含欧元和美分的价格的价格值。在Rails框架中处理货币方面,我是否遗漏了一些明显的东西?


您可能需要在数据库中使用DECIMAL类型。在迁移过程中,请执行以下操作:

1
2
3
# precision is the total number of digits
# scale is the number of digits to the right of the decimal point
add_column :items, :price, :decimal, :precision => 8, :scale => 2

在rails中,:decimal类型返回为BigDecimal类型,这对于价格计算非常有用。

如果您坚持使用整数,则必须在任何地方手动转换到和从BigDecimals转换,这可能只是一种痛苦。

如MCL所指出,要打印价格,请使用:

1
2
number_to_currency(price, :unit =>"€")
#=> €1,234.01


这里有一个很好的、简单的方法,它利用了composed_of(activerecord的一部分,使用valueobject模式)和money gem

你需要

  • 货币宝石(4.1.0版)
  • 一种模型,例如Product
  • 模型(和数据库)中的integer列,例如:price

把这个写在你的product.rb文件中:

1
2
3
4
5
6
7
class Product > ActiveRecord::Base

  composed_of :price,
              :class_name => 'Money',
              :mapping => %w(price cents),
              :converter => Proc.new { |value| Money.new(value) }
  # ...

你将得到:

  • 如果没有任何额外的更改,您的所有表单都将显示美元和美分,但内部表示仍然只是美分。表单将接受类似于"12034.95美元"的值,并为您转换它。不需要向模型或视图中的助手添加额外的处理程序或属性。
  • product.price ="$12.00"自动转换为货币类
  • product.price.to_s显示十进制格式的数字("1234.00")。
  • product.price.format为货币显示格式正确的字符串。
  • 如果您需要发送分(到需要分的支付网关),product.price.cents.to_s
  • 免费货币兑换


处理货币的常见做法是使用十进制类型。下面是一个简单的例子,"使用Rails的敏捷Web开发"

1
add_column :products, :price, :decimal, :precision => 8, :scale => 2

这将允许您处理从-999999.99到999999.99的价格。您可能还希望在项目中包括验证,如

1
2
3
def validate
  errors.add(:price,"should be at least 0.01") if price.nil? || price < 0.01
end

为了理智,检查你的价值观。


使用金钱轨道宝石。它可以很好地处理您的模型中的货币和货币,并且有许多帮助者来格式化您的价格。


如果您正在使用Postgres(既然我们现在在2017年),那么您可能会尝试使用他们的:money列类型A。

1
add_column :products, :price, :money, default: 0

使用虚拟属性(链接到修订(付费)RailsCast),您可以在整型列中以美分为单位存储价格,并在产品模型中添加以美元为单位的虚拟属性价格作为getter和setter。

1
2
3
4
5
6
7
8
9
10
11
12
13
# Add a price_in_cents integer column
$ rails g migration add_price_in_cents_to_products price_in_cents:integer

# Use virtual attributes in your Product model
# app/models/product.rb

def price_in_dollars
  price_in_cents.to_d/100 if price_in_cents
end

def price_in_dollars=(dollars)
  self.price_in_cents = dollars.to_d*100 if dollars.present?
end

来源:railscapsts 016:虚拟属性:虚拟属性是一种干净的方式,可以添加不直接映射到数据库的表单字段。在这里,我将演示如何处理验证、关联等。


我使用它的方式是:

1
number_to_currency(amount, unit: '€', precision: 2, format:"%u %n")

当然,货币符号、精度、格式等都取决于每种货币。


如果有人正在使用Sequel,那么迁移看起来就像:

1
add_column :products, :price,"decimal(8,2)"

不知怎的,续集忽略了:精度和:尺度

(续集版本:续集(3.39.0,3.38.0))


绝对是整数。

即使bigdecimal技术上存在,1.5仍然会给你一个纯的红宝石浮点。


我的底层API都使用美分来表示钱,我不想改变这一点。我也没有用大量的钱工作。所以我把它放在一个助手方法中:

1
sprintf("%03d", amount).insert(-3,".")

它将整数转换为至少有三位数字的字符串(如有必要,可添加前导零),然后在最后两位数字之前插入小数点,而不使用Float。从那里,您可以添加任何适合您的用例的货币符号。

它肯定又快又脏,但有时也很好!


您可以将一些选项传递给number_to_currency(标准Rails 4视图助手):

1
2
number_to_currency(12.0, :precision => 2)
# =>"$12.00"

正如Dylan Markow发布的


对于一些有抱负的RoR开发中的初级/初级学生来说,这只是一个小小的更新和所有答案的内聚,肯定会有一些解释。

用钱工作

按照@molf的建议,使用:decimal将钱存储在数据库中(以及我的公司在使用钱时使用的黄金标准)。

1
2
3
# precision is the total number of digits
# scale is the number of digits to the right of the decimal point
add_column :items, :price, :decimal, precision: 8, scale: 2

几点:

  • :decimal将作为BigDecimal使用,解决了很多问题。

  • precisionscale应根据您所代表的内容进行调整。

    • 如果你在收付工作中,precision: 8scale: 2给予你最高的金额,在90%的情况下罚款。

    • 如果你需要代表一个财产或稀有汽车的价值,你应该使用更高的precision

    • 如果你使用坐标(经度和纬度),你肯定需要一个更高的scale

如何生成迁移

要使用上述内容生成迁移,请在终端中运行:

1
bin/rails g migration AddPriceToItems price:decimal{8-2}

1
bin/rails g migration AddPriceToItems 'price:decimal{5,2}'

如本文所述。

货币格式

与额外的库吻别,使用内置的帮助器。使用number_to_currency作为@molf和@facundfarias的建议。

要在Rails控制台中使用number_to_currency助手,请向ActiveSupportNumberHelper类发送一个调用,以便访问助手。

例如:

1
ActiveSupport::NumberHelper.number_to_currency(2_500_000.61, unit: '€', precision: 2, separator: ',', delimiter: '', format:"%n%u")

给出以下输出

1
2500000,61

检查另一个options的数字"u to"货币助手。

把它放在哪里

您可以将它放在应用程序帮助程序中,并在视图中以任意数量使用它。

1
2
3
4
5
module ApplicationHelper    
  def format_currency(amount)
    number_to_currency(amount, unit: '€', precision: 2, separator: ',', delimiter: '', format:"%n%u")
  end
end

或者您可以将它作为一个实例方法放在Item模型中,并在需要格式化价格的地方调用它(在视图或助手中)。

1
2
3
4
5
class Item < ActiveRecord::Base
  def format_price
    number_to_currency(price, unit: '€', precision: 2, separator: ',', delimiter: '', format:"%n%u")
  end
end

还有一个例子,我如何在控制者内部使用number_to_currency(注意,negative_format选项,用于表示退款)

1
2
3
4
5
6
7
8
9
def refund_information
  amount_formatted =
    ActionController::Base.helpers.number_to_currency(@refund.amount, negative_format: '(%u%n)')
  {
    # ...
    amount_formatted: amount_formatted,
    # ...
  }
end

Ruby&Rails的简单代码

1
2
3
<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50