处理货币的最好方法是什么?

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

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

我在视图中显示包含欧元和美分的价格时遇到了麻烦。在Rails框架中处理货币时,我是否遗漏了一些明显的东西?

202339 次浏览
处理货币的常见做法是使用十进制类型。 下面是一个简单的例子,来自“敏捷Web开发与Rails”

add_column :products, :price, :decimal, :precision => 8, :scale => 2
这将允许您处理价格从-999,999.99到999,999.99
您可能还希望在您的项目中包含验证,如

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

检查你的价值观。

你可能想在你的数据库中使用DECIMAL类型。在迁移过程中,执行如下操作:

# 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,这对于价格计算非常有用。

如果你坚持使用整数,你将不得不在所有地方手动转换__abc,这可能会成为一个麻烦。

正如mcl所指出的,要打印价格,请使用:

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

这里有一个很好的,简单的方法,它利用composed_of (ActiveRecord的一部分,使用ValueObject模式)和Money宝石

你需要

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

写在你的product.rb文件中:

class Product > ActiveRecord::Base


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

你会得到:

  • 在没有任何额外更改的情况下,所有表单都将显示美元和美分,但内部表示仍然只是美分。表单将接受“$12,034.95”这样的值,并为您转换它。没有必要向您的模型或视图中添加额外的处理程序或属性。
  • product.price = "$12.00"自动转换为Money类
  • product.price.to_s显示十进制格式的数字("1234.00")
  • product.price.format显示正确格式化的货币字符串
  • 如果你需要发送美分(到一个想要便士的支付网关),product.price.cents.to_s
  • 免费货币转换

绝对整数

即使BigDecimal技术上存在1.5仍然会在Ruby中给你一个纯Float。

使用虚拟属性(链接到修订的(付费)Railscast),你可以将你的price_in_cents存储在一个整数列中,并在你的产品模型中添加一个虚拟属性price_in_dollars作为getter和setter。

# 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

来源:RailsCasts #016: 虚拟属性: 虚拟属性是添加不直接映射到数据库的表单字段的一种简洁方法。在这里,我将展示如何处理验证、关联等。

如果有人正在使用Sequel,那么迁移将会是这样的:

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

Sequel忽略了:precision和:scale

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

你可以将一些选项传递给number_to_currency(一个标准的Rails 4视图帮助器):

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

迪伦Markow发布

使用money-rails宝石。它在你的模型中很好地处理金钱和货币,也有一堆帮助来格式化你的价格。

我的底层api都使用美分来表示钱,我不想改变这一点。我也没有大量的资金。我把这个放到helper方法中

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

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

它是肯定快速和肮脏,但有时这是好的!

我是这样用的:

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

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

如果你正在使用Postgres(因为我们现在是在2017年),你可能想尝试一下他们的:money列类型。

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

Ruby &Rails

<%= number_to_currency(1234567890.50) %>


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

只是对RoR开发中一些有抱负的初级/初学者的所有答案进行了一点更新和整合,这些答案肯定会在这里得到一些解释。

与金钱打交道

使用:decimal在DB中存储货币,正如@molf所建议的那样(也是我的公司在处理货币时使用的黄金标准)。

# 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会给你999,999.99作为最高的金额,这在90%的情况下是正常的。

    • 如果你需要表示一个属性或稀有汽车的值,你应该使用更高的precision

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

如何生成迁移

要生成带有上述内容的迁移,在terminal中运行:

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

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

正如在博客帖子中解释的那样。

货币格式

多余的库再见,并使用内置的helper。按照@molf和@facundofarias的建议使用number_to_currency

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

例如:

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

给出以下输出

2500000,61€

检查number_to_currency helper的另一个options

放在哪里

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

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

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

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选项,用于表示退款)

def refund_information
amount_formatted =
ActionController::Base.helpers.number_to_currency(@refund.amount, negative_format: '(%u%n)')
{
# ...
amount_formatted: amount_formatted,
# ...
}
end