通貨/お金を処理する最良の方法は何ですか?


323

私は非常に基本的なショッピングカートシステムに取り組んでいます。

タイプのitems列を持つテーブルがあります。priceinteger

ユーロとセントの両方を含む価格のビューに価格値を表示するのに問題があります。Railsフレームワークで通貨を処理することに関して、私は明白な何かを見逃していますか?


誰かが使用するSQL場合は、DECIMAL(19, 4) 一般的な選択肢であるチェック、これはまた、チェック、ここでどのように多くの小数点以下の桁使用することを決定するために世界の通貨書式を、希望が役立ちます。
shaijut 2015年

回答:


495

おそらく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ため、価格計算に最適です。

整数の使用を主張する場合は、BigDecimalどこでもs との間で手動で変換する必要がありますが、これはおそらく面倒になるだけです。

mclで指摘されているように、価格を出力するには、次のコマンドを使用します。

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

13
number_to_currencyヘルパーを使用します。詳細については、api.rubyonrails.org
classes /

48
実際、acts_as_dollarsと組み合わせて整数を使用する方がはるかに安全で簡単です。浮動小数点比較に噛まれたことはありますか?そうでない場合は、これを最初の経験にしないでください。:) acts_as_dollarsを使用して、12.34形式で入力し、1234として保存され、12.34として出力されます。
サラ・メイ、

50
@Sarah Mei:BigDecimals + 10進数の列形式は、それを正確に回避します。
molf 2009年

114
この答えを盲目的にコピーしないことが重要です。精度8、スケール2では最大値999,999.99が得られます。100万を超える数が必要な場合は、精度を上げてください。
ジョンケアンズ2013年

22
異なる通貨を扱う場合は、単に2のスケールを使用しないことも重要です。オマーンリアルやチュニジアディナールなどの一部の北アフリカとアラブの通貨のスケールは3なので、精度8のスケール3がより適切です。
Richartzを

117

これが活用する細かい、シンプルなアプローチです composed_of(ValueObjectパターンを使用したActiveRecordの一部)と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) }
  # ...

あなたが得るもの:

  • 追加の変更がなければ、すべてのフォームにドルとセントが表示されますが、内部表現は依然としてセントです。フォームは「$ 12,034.95」などの値を受け入れ、変換します。モデルにハンドラーや属性を追加したり、ビューのヘルパーを追加したりする必要はありません。
  • product.price = "$12.00" 自動的にMoneyクラスに変換します
  • product.price.to_s 10進数形式の数値を表示します( "1234.00")
  • product.price.format 通貨の適切にフォーマットされた文字列を表示します
  • セントを送る必要がある場合(ペニーを必要とする支払いゲートウェイに)、 product.price.cents.to_s
  • 無料の通貨換算

14
私はこのアプローチが大好きです。ただし、注意してください。この例の「price」の移行では、nullが許可されておらず、デフォルトで0になっていることを確認してください。
コリー

3
私はmoney_column gemShopifyから抽出)が非常に簡単に使用できることを発見しました。通貨換算が必要ない場合は、money gcolumnよりも簡単です。
タリル語2011

7
Railsコアチームがフレームワークからの "composed_of"の非推奨化と削除について議論していることは、Moneyジェムを使用するすべての人にとって注意すべきです。これが発生した場合、gemはこれを処理するように更新されると思いますが、Rails 4.0を検討している場合は、この可能性に注意する必要があります
Peer Allan

1
composed_of ここでの削除に関する@PeerAllanのコメントについては、その詳細と代替実装について詳しく説明しています。
HerbCSO 2013年

3
また、これはrails-moneyジェムを使用すると本当に危険です。
fotanus 2014

25

通貨を処理する一般的な方法は、10進数タイプを使用することです。以下は、「Railsを使用したアジャイルWeb開発」の簡単な例です。

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 

値の健全性をチェックします。


1
このソリューションでは、SQLの合計などを使用することもできます。
ラリーK

4
可能性があります:validates:price、:presence => true、:numericality => {:greater_than => 0}
Galaxy

9

Postgresを使用している場合(そして、現在は2017年なので)、:money列タイプを試してみるとよいでしょう。

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

7

money-rails gemを使用してください。それはあなたのモデルのお金と通貨をうまく処理し、あなたの価格をフォーマットするヘルパーの束も持っています。


ええ、私はこれに同意します。一般的に、私はそれをセント(整数)として格納し、acts-as-moneyまたはmoney(money-rails)のようなgemを使用してデータをメモリ内で処理することにより、お金を処理します。整数で処理することで、これらの厄介な丸めエラーが防止されます。例0.2 * 3 => 0.6000000000000001もちろん、これは1セントの端数を処理する必要がない場合にのみ機能します。
Chad M

これは、レールを使用している場合に非常に便利です。それを入れて、10進数の列の問題について心配しないでください。ビューでこれを使用する場合は、この答えは、同様に有用である可能性がありますstackoverflow.com/questions/18898947/...
mooreds

6

ほんの少しの更新と、いくつかの説明のために確実にここに来るRoR開発の意欲的なジュニア/初心者のためのすべての答えのまとまり。

お金を扱う

:decimal@molfが示唆するように、DBにお金を格納するために使用します(そして、私の会社がお金を扱うときに黄金の標準として使用するもの)。

# 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

いくつかのポイント:

  • :decimalBigDecimal多くの問題を解決するものとして使用されます。

  • precisionそしてscale、あなたが何を表しているかに応じて、調整する必要があります

    • 支払いの受け取りと送金を処理し、最高額precision: 8scale: 2提供する999,999.99場合、90%の場合は問題ありません。

    • プロパティや希少車の値を表す必要がある場合は、より高い値を使用する必要がありますprecision

    • 座標(経度と緯度)を扱う場合は、より高い値が必要になりscaleます。

移行を生成する方法

上記の内容で移行を生成するには、ターミナルで実行します:

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

または

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

このブログで説明されているように投稿でます。

通貨のフォーマット

KISSの余分なライブラリは別れと組み込みのヘルパーを使用します。使用するnumber_to_currency@molfおよび@facundofariasの推奨どおりに。

一緒にプレイするにはnumber_to_currencyRailsのコンソールでヘルパーに呼び出しを送るActiveSupportさんNumberHelperクラスにてヘルパーにアクセスします。

例えば:

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

次の出力を与える

2500000,61

number_to_currencyのもう一方optionsを確認してくださいヘルパー。

どこに置くか

アプリケーションヘルパーに入れて、ビュー内で任意の量を使用できます。

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

またはItem、インスタンスメソッドとしてモデルに配置し、価格をフォーマットする必要がある場所(ビューまたはヘルパー)で呼び出すことができます。

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

5

使用して仮想属性(改訂版(有償)Railscastへのリンクを)あなたは、整数列であなたのprice_in_centsを保存し、ゲッターとセッターとしてご使用の製品モデルに仮想属性price_in_dollarsを追加することができます。

# 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:仮想属性仮想属性は、データベースに直接マップしないフォームフィールドを追加するためのクリーンな方法です。ここでは、検証、関連付けなどの処理方法を示します。


1
これにより、200.0が1桁
残ります

2

間違いなく整数

そして、BigDecimalが技術的に存在していても1.5、Rubyで純粋なFloatを提供します。


2

誰かがSequelを使用している場合、移行は次のようになります。

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

どういうわけかSequelは:precisionと:scaleを無視します

(続編:続編(3.39.0、3.38.0))


2

私の基になるAPIはすべてセントを使ってお金を表していたので、それを変更したくありませんでした。また、私は多額のお金で働いていませんでした。だから私はこれをヘルパーメソッドに入れます:

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

これにより、整数が少なくとも3桁の文字列に変換され(必要に応じて先行ゼロが追加されます)、最後の2桁の前に小数点が挿入されFloatます。そこから、ユースケースに適した通貨記号を追加できます。

それは間違いなく迅速で汚いですが、時にはそれで十分です!


誰もあなたを賛成しなかったなんて信じられない。これは、私のMoneyオブジェクトをAPIが受け取ることができるような形式にうまく変換するために機能した唯一のものでした。(10進数)
Code-MonKy 2017

2

私はそれをこのように使用しています:

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

もちろん、通貨記号、精度、形式などは各通貨によって異なります。


1

一部のオプションをnumber_to_currency(標準のRails 4ビューヘルパー)に渡すことができます。

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

ディラン・マルコウによる投稿


0

Ruby&Railsのシンプルなコード

<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.