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

Bar*_*her 316 ruby currency ruby-on-rails

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

我有一个items具有price类型列的表integer.

我无法在包括欧元和美分在内的价格中显示价格值.就Rails框架中的处理货币而言,我是否遗漏了一些明显的东西?

mol*_*olf 485

您可能希望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
Run Code Online (Sandbox Code Playgroud)

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

如果你坚持使用整数,你将不得不手动转换到BigDecimal任何地方,这可能只是一个痛苦.

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

number_to_currency(price, :unit => "€")
#=> €1,234.01
Run Code Online (Sandbox Code Playgroud)

  • 重要的是不要盲目地复制这个答案 - *精度8,比例2*给出最大值**999,999.99**.如果您需要一个大于一百万的数字,那么提高精度! (110认同)
  • @Sarah Mei:BigDecimals +十进制列格式正好避免了. (50认同)
  • 实际上,将一个整数与acts_as_dollars结合使用会更安全,更容易.你有没有被浮点比较咬伤?如果没有,请不要将此作为您的第一次体验.:)使用acts_as_dollars,你把内容放入12.34格式,它存储为1234,它出现为12.34. (47认同)
  • 如果你处理不同的货币,不仅盲目地使用2的等级也很重要 - 一些北非和阿拉伯货币如阿曼里亚尔或突尼斯第纳尔的等级为3,因此*精度8等级3*更多适合那里. (22认同)
  • 使用number_to_currency帮助程序,更多信息,请访问http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#M001969 (13认同)
  • 根据我的经验,当你做很多算术时,BigDecimal会变得非常痛苦.使用的有效数字的数量会上升,并且变得非常慢(我在某处读到性能为O(n),其中n =数字位数).一个简单的`BigDecimal('12 .34')*BigDecimal('1.19').power(5)/ BigDecimal(3)`导致BigD使用45位有效数字.为了避免这种情况,你必须编写`BigDecimal('12 .34').mult(BigDecimal('1.19').power(5),10).div(BigDecimal(3),10)` (4认同)
  • 如果要在模型生成步骤中添加精度和比例,则必须将参数包装在引号中!**错误:**`rails g model项目价格:十进制{8,2}`.**正确:**`rails g model Item'价格:十进制{8,2}'`.这已在[Rails使用文档](https://github.com/rails/rails/pull/12642/files)中修复.如果您不引用它,您的迁移文件将如下所示:`t.decimal8:price,t.decimal2:price`并导致错误. (4认同)
  • 还有一个Money gem,Money类可以使用serialized或composed_of存储为文本列 (3认同)
  • 使用Integers来存储会更安全.查看RubyMoney Gem:https://github.com/RubyMoney/money-rails (3认同)
  • 了解您的类型转换.如果你进行任何类型的计算,你必须确保没有隐式转换为Float(例如BigDecimal*Float => Float).我最终在我的代码中做了很多"0.05.to_d",以确保我在所有BigDecimal世界中工作. (3认同)

Ken*_*yer 113

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

你需要

  • 金钱的宝石(版本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) }
  # ...
Run Code Online (Sandbox Code Playgroud)

你会得到什么:

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

  • 我喜欢这种方法.但请注意:请确保您在此示例中的"价格"迁移不允许空值,默认为0,以免您疯狂地试图弄清楚为什么这不起作用. (14认同)
  • 对于使用Money gem的所有人,应该注意Rails核心团队正在讨论弃用并从框架中删除"composed_of".我怀疑gem会更新以便在发生这种情况时进行处理,但如果您正在查看Rails 4.0,您应该知道这种可能性 (7认同)
  • 我发现[money_column gem](https://github.com/tobi/money_column)(从Shopify中提取)非常直接使用...比金钱宝石更容易,如果你不需要货币转换. (3认同)
  • 而且,使用[rails-money](https://github.com/RubyMoney/money-rails)gem真的很难过. (3认同)

ale*_*dev 25

处理货币的常用做法是使用十进制类型.以下是"使用Rails进行敏捷Web开发"的简单示例

add_column :products, :price, :decimal, :precision => 8, :scale => 2 
Run Code Online (Sandbox Code Playgroud)

这将允许您处理从-999,999.99到999,999.99的价格.
您可能还想在您的项目中包含验证

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

理智 - 检查你的价值观.

  • 您可以这样做:验证:price,:presence => true,:numericality => {:greater_than => 0} (4认同)

小智 12

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

用钱工作

用于:decimal在数据库中存储资金,正如@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
Run Code Online (Sandbox Code Playgroud)

几点:

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

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

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

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

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

如何生成迁移

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

bin/rails g migration AddPriceToItems price:decimal{8-2}
Run Code Online (Sandbox Code Playgroud)

或者

bin/rails g migration AddPriceToItems 'price:decimal{5,2}'
Run Code Online (Sandbox Code Playgroud)

如本文中所述。

货币格式

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

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

例如:

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

给出以下输出

2500000,61€
Run Code Online (Sandbox Code Playgroud)

检查其他optionsnumber_to_currency帮手。

把它放在哪里

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

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

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

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

并且,我如何使用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
Run Code Online (Sandbox Code Playgroud)


Tro*_*ggy 7

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


The*_* Oz 7

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

add_column :products, :price, :money, default: 0
Run Code Online (Sandbox Code Playgroud)


Tho*_*emm 5

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

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