Ruby-on-rails 处理货币/金钱的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1019939/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 21:23:10  来源:igfitidea点击:

What is the best method of handling currency/money?

ruby-on-railsrubycurrency

提问by Barry Gallagher

I'm working on a very basic shopping cart system.

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

I have a table itemsthat has a column priceof type integer.

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

I'm having trouble displaying the price value in my views for prices that include both Euros and cents. Am I missing something obvious as far as handling currency in the Rails framework is concerned?

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

回答by molf

You'll probably want to use a DECIMALtype in your database. In your migration, do something like this:

您可能希望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

In Rails, the :decimaltype is returned as BigDecimal, which is great for price calculation.

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

If you insist on using integers, you will have to manually convert to and from BigDecimals everywhere, which will probably just become a pain.

如果您坚持使用整数,您将不得不在BigDecimal任何地方手动转换s和从s转换,这可能会变得很痛苦。

As pointed out by mcl, to print the price, use:

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

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

回答by Ken Mayer

Here's a fine, simple approach that leverages composed_of(part of ActiveRecord, using the ValueObject pattern) and the Money gem

这是一个很好的、简单的方法,它利用composed_of(ActiveRecord 的一部分,使用 ValueObject 模式)和 Money gem

You'll need

你需要

  • The Money gem(version 4.1.0)
  • A model, for example Product
  • An integercolumn in your model (and database), for example :price
  • 金钱的宝石(版本4.1.0)
  • 一个模型,例如 Product
  • 例如integer,模型(和数据库)中的一列:price

Write this in your product.rbfile:

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

class Product > ActiveRecord::Base

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

What you'll get:

你会得到什么:

  • Without any extra changes, all of your forms will show dollars and cents, but the internal representation is still just cents. The forms will accept values like "$12,034.95" and convert it for you. There's no need to add extra handlers or attributes to your model, or helpers in your view.
  • product.price = "$12.00"automatically converts to the Money class
  • product.price.to_sdisplays a decimal formatted number ("1234.00")
  • product.price.formatdisplays a properly formatted string for the currency
  • If you need to send cents (to a payment gateway that wants pennies), product.price.cents.to_s
  • Currency conversion for free
  • 如果没有任何额外的更改,您的所有表单都将显示美元和美分,但内部表示仍然只是美分。表单将接受诸如“$12,034.95”之类的值并为您进行转换。无需向模型或视图中的助手添加额外的处理程序或属性。
  • product.price = "$12.00"自动转换为 Money 类
  • product.price.to_s显示十进制格式的数字(“1234.00”)
  • product.price.format显示正确格式的货币字符串
  • 如果您需要发送美分(到需要便士的支付网关), product.price.cents.to_s
  • 免费货币兑换

回答by alex.zherdev

Common practice for handling currency is to use decimal type. Here is a simple example from "Agile Web Development with Rails"

处理货币的常见做法是使用十进制类型。这是“使用 Rails 进行敏捷 Web 开发”中的一个简单示例

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

This will allow you to handle prices from -999,999.99 to 999,999.99
You may also want to include a validation in your items like

这将允许您处理从 -999,999.99 到 999,999.99 的价格
您可能还希望在您的项目中包含一个验证,例如

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

to sanity-check your values.

理智检查你的价值观。

回答by The Whiz of Oz

If you are using Postgres (and since we're in 2017 now) you might want to give their :moneycolumn type a try.

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

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

回答by Troggy

Use money-rails gem. It nicely handles money and currencies in your model and also has a bunch of helpers to format your prices.

使用money-rails gem。它可以很好地处理您模型中的货币和货币,并且还有一堆帮助程序来格式化您的价格。

回答by Thomas Klemm

Using Virtual Attributes (Link to revised(paid) Railscast)you can store your price_in_cents in an integer column and add a virtual attribute price_in_dollars in your product model as a getter and setter.

使用虚拟属性(链接到修订(付费)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

Source: RailsCasts #016: Virtual Attributes: Virtual attributes are a clean way to add form fields that do not map directly to the database. Here I show how to handle validations, associations, and more.

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

回答by Zlatko Alomerovic

Just a little update and a cohesion of all the answers for some aspiring juniors/beginners in RoR development that will surely come here for some explanations.

对于一些有抱负的 RoR 开发的初级/初学者来说,只是一点点更新和所有答案的凝聚力,他们肯定会来这里做一些解释。

Working with money

用钱工作

Use :decimalto store money in the DB, as @molf suggested (and what my company uses as a golden standard when working with money).

使用: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

Few points:

几点:

  • :decimalis going to be used as BigDecimalwhich solves a lot of issues.

  • precisionand scaleshould be adjusted, depending on what you are representing

    • If you work with receiving and sending payments, precision: 8and scale: 2gives you 999,999.99as the highest amount, which is fine in 90% of cases.

    • If you need to represent the value of a property or a rare car, you should use a higher precision.

    • If you work with coordinates (longitude and latitude), you will surely need a higher scale.

  • :decimal将用于BigDecimal解决很多问题。

  • precision并且scale应该根据您所代表的内容进行调整

    • 如果您处理接收和发送付款,precision: 8并将scale: 2999,999.99作为最高金额,则在 90% 的情况下都可以。

    • 如果您需要表示财产或稀有汽车的价值,则应使用更高的precision.

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

How to generate a migration

如何生成迁移

To generate the migration with the above content, run in terminal:

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

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

or

或者

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

as explained in this blogpost.

如本文中所述。

Currency formatting

货币格式

KISSthe extra libraries goodbye and use built-in helpers. Use number_to_currencyas @molf and @facundofarias suggested.

KISS额外库再见,使用内置的助手。使用number_to_currency@molf 和@facundofarias 建议。

To play with number_to_currencyhelper in Rails console, send a call to the ActiveSupport's NumberHelperclass in order to access the helper.

number_to_currency在 Rails 控制台中使用helper,请向ActiveSupport'sNumberHelper类发送调用以访问 helper。

For example:

例如:

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

gives the following output

给出以下输出

2500000,61

Check the other optionsof number_to_currencyhelper.

检查其他optionsnumber_to_currency帮手。

Where to put it

把它放在哪里

You can put it in an application helper and use it inside views for any amount.

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

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

Or you can put it in the Itemmodel as an instance method, and call it where you need to format the price (in views or helpers).

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

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

And, an example how I use the number_to_currencyinside a contrroler (notice the negative_formatoption, used to represent refunds)

并且,我如何使用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

回答by jethroo

If someone is using Sequel the migration would look something like:

如果有人使用 Sequel,迁移将类似于:

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

somehow Sequel ignores :precision and :scale

不知何故 Sequel 忽略了 :precision 和 :scale

(Sequel Version: sequel (3.39.0, 3.38.0))

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

回答by Brent Royal-Gordon

My underlying APIs were all using cents to represent money, and I didn't want to change that. Nor was I working with large amounts of money. So I just put this in a helper method:

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

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

That converts the integer to a string with at least three digits (adding leading zeroes if necessary), then inserts a decimal point before the last two digits, never using a Float. From there you can add whatever currency symbols are appropriate for your use case.

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

It's definitelyquick and dirty, but sometimes that's just fine!

绝对又快又脏,但有时这很好!

回答by facundofarias

I am using it on this way:

我正在以这种方式使用它:

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

Of course that the currency symbol, precision, format and so on depends on each currency.

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