Was ist die beste Methode, um mit Währung / Geld umzugehen?

323

Ich arbeite an einem sehr einfachen Warenkorbsystem.

Ich habe eine Tabelle itemsmit einer Spalte pricevom Typ integer.

Ich habe Probleme, den Preiswert in meinen Ansichten für Preise anzuzeigen, die sowohl Euro als auch Cent enthalten. Vermisse ich etwas Offensichtliches in Bezug auf den Umgang mit Währungen im Rails-Framework?

Barry Gallagher
quelle
Wenn jemand SQL verwendet, DECIMAL(19, 4) ist dies eine beliebte Option. Überprüfen Sie dies auch hier. Weltwährungsformate, um zu entscheiden, wie viele Dezimalstellen verwendet werden sollen. Hoffnung hilft.
Shaijut

Antworten:

495

Sie möchten wahrscheinlich einen DECIMALTyp in Ihrer Datenbank verwenden. Gehen Sie bei Ihrer Migration folgendermaßen vor:

# 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 wird der :decimalTyp als zurückgegeben BigDecimal, was sich hervorragend für die Preisberechnung eignet.

Wenn Sie darauf bestehen, Ganzzahlen zu verwenden, müssen Sie BigDecimalüberall manuell von und nach s konvertieren , was wahrscheinlich nur zu einem Problem wird.

Verwenden Sie zum Drucken des Preises: mcl:

number_to_currency(price, :unit => "€")
#=> €1,234.01
molf
quelle
13
Verwenden Sie den Helfer number_to_currency, weitere Informationen unter api.rubyonrails.org/classes/ActionView/Helpers/…
mlibby
48
Tatsächlich ist es viel sicherer und einfacher, eine Ganzzahl in Kombination mit Acts_as_Dollars zu verwenden. Wurden Sie jemals durch einen Gleitkomma-Vergleich gebissen? Wenn nicht, machen Sie dies nicht zu Ihrer ersten Erfahrung. :) Mit Acts_as_dollars legen Sie Inhalte im 12.34-Format ab, sie werden als 1234 gespeichert und als 12.34 ausgegeben.
Sarah Mei
50
@ Sarah Mei: BigDecimals + Dezimalspaltenformat vermeidet genau das.
Wolf
114
Es ist wichtig, diese Antwort nicht nur blind zu kopieren - Präzision 8, Skala 2 ergibt einen Maximalwert von 999.999,99 . Wenn Sie eine Zahl größer als eine Million benötigen, erhöhen Sie die Präzision!
Jon Cairns
22
Es ist auch wichtig, nicht nur blind eine Skala von 2 zu verwenden, wenn Sie mit verschiedenen Währungen umgehen - einige nordafrikanische und arabische Währungen wie der Omani Rial oder der Tunesische Dinar haben eine Skala von 3, daher ist Präzision 8 Skala 3 dort besser geeignet .
Schlagen Sie Richartz
117

Hier ist ein feiner, einfacher Ansatz, der composed_of(Teil von ActiveRecord, unter Verwendung des ValueObject-Musters) und das Geld-Juwel nutzt

Du brauchst

  • Das Geldjuwel (Version 4.1.0)
  • Ein Modell zum Beispiel Product
  • integerZum Beispiel eine Spalte in Ihrem Modell (und Ihrer Datenbank):price

Schreiben Sie dies in Ihre product.rbDatei:

class Product > ActiveRecord::Base

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

Was Sie bekommen:

  • Ohne zusätzliche Änderungen werden in allen Formularen Dollar und Cent angezeigt, aber die interne Darstellung besteht immer noch nur aus Cent. Die Formulare akzeptieren Werte wie "12.034,95 USD" und konvertieren sie für Sie. Sie müssen Ihrem Modell keine zusätzlichen Handler oder Attribute oder Helfer in Ihrer Ansicht hinzufügen.
  • product.price = "$12.00" konvertiert automatisch in die Money-Klasse
  • product.price.to_s zeigt eine dezimal formatierte Zahl an ("1234.00")
  • product.price.format Zeigt eine ordnungsgemäß formatierte Zeichenfolge für die Währung an
  • Wenn Sie Cent senden müssen (an ein Zahlungsgateway, das ein paar Cent möchte), product.price.cents.to_s
  • Währungsumrechnung kostenlos
Ken Mayer
quelle
14
Ich liebe diesen Ansatz. Beachten Sie jedoch Folgendes: Stellen Sie sicher, dass Ihre Migration für "Preis" in diesem Beispiel keine Nullen und Standardeinstellungen von 0 zulässt, damit Sie nicht verrückt werden und herausfinden, warum dies nicht funktioniert.
Cory
3
Ich fand, dass der Edelstein money_column (aus Shopify extrahiert) sehr einfach zu verwenden ist ... einfacher als der Edelstein Geld, wenn Sie keine Währungsumrechnung benötigen.
Talyric
7
Für alle, die das Money-Juwel verwenden, sollte beachtet werden, dass das Rails-Kernteam das Verwerfen und Entfernen von "compos_of" aus dem Framework diskutiert. Ich vermute, dass der Edelstein aktualisiert wird, um dies zu handhaben, wenn es passiert, aber wenn Sie sich Rails 4.0 ansehen, sollten Sie sich dieser Möglichkeit bewusst sein
Peer Allan
1
In Bezug auf den Kommentar von @ PeerAllan zum Entfernen von composed_of hier werden weitere Details dazu sowie eine alternative Implementierung angegeben.
HerbCSO
3
Auch das ist mit dem Rails-Money- Juwel sehr einfach .
Fotanus
25

Für den Umgang mit Währungen ist es üblich, einen Dezimaltyp zu verwenden. Hier ist ein einfaches Beispiel aus "Agile Webentwicklung mit Schienen"

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

Auf diese Weise können Sie Preise von -999.999,99 bis 999.999,99 verarbeiten.
Möglicherweise möchten Sie auch eine Validierung in Ihre Artikel wie

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

um Ihre Werte zu überprüfen.

alex.zherdev
quelle
1
Mit dieser Lösung können Sie auch SQL-Summe und Freunde verwenden.
Larry K
4
Könnten Sie möglicherweise tun: validiert: Preis ,: Präsenz => wahr ,: Numerizität => {: größer_ als => 0}
Galaxy
8

Wenn Sie Postgres verwenden (und seit wir 2017 sind), sollten Sie den Spaltentyp :moneyausprobieren.

add_column :products, :price, :money, default: 0
Der Zauber von Oz
quelle
7

Verwenden Sie Geldschienen Edelstein . Es handhabt Geld und Währungen in Ihrem Modell gut und hat auch eine Reihe von Helfern, um Ihre Preise zu formatieren.

Troggy
quelle
Ja, dem stimme ich zu. Im Allgemeinen verarbeite ich Geld, indem ich es als Cent (Ganzzahl) speichere und einen Edelstein wie "Acts-as-Money" oder "Money (Money-Rails)" verwende, um die Daten im Speicher zu verarbeiten. Die Behandlung in ganzen Zahlen verhindert diese bösen Rundungsfehler. ZB 0,2 * 3 => 0,6000000000000001 Dies funktioniert natürlich nur, wenn Sie keine Bruchteile eines Cent verarbeiten müssen.
Chad M
Dies ist sehr schön, wenn Sie Schienen verwenden. Legen Sie es ab und sorgen Sie sich nicht um die Probleme mit einer Dezimalspalte. Wenn Sie dies mit einer Ansicht verwenden, kann diese Antwort auch hilfreich sein: stackoverflow.com/questions/18898947/…
festgemacht
6

Nur ein kleines Update und ein Zusammenhalt aller Antworten für einige aufstrebende Junioren / Anfänger in der RoR-Entwicklung, die sicherlich hier für einige Erklärungen kommen werden.

Mit Geld arbeiten

Verwenden Sie :decimaldiese Option, um Geld in der Datenbank zu speichern, wie @molf vorgeschlagen hat (und was mein Unternehmen als goldenen Standard für die Arbeit mit Geld verwendet).

# 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

Einige Punkte:

  • :decimalwird verwendet, um BigDecimalviele Probleme zu lösen.

  • precisionund scalesollte angepasst werden, je nachdem, was Sie darstellen

    • Wenn Sie mit dem Empfangen und Senden von Zahlungen arbeiten precision: 8und scale: 2Ihnen 999,999.99den höchsten Betrag geben, ist dies in 90% der Fälle in Ordnung.

    • Wenn Sie den Wert einer Immobilie oder eines seltenen Autos darstellen müssen, sollten Sie einen höheren Wert verwenden precision.

    • Wenn Sie mit Koordinaten (Längen- und Breitengrad) arbeiten, benötigen Sie sicherlich einen höheren scale.

So generieren Sie eine Migration

Führen Sie im Terminal Folgendes aus, um die Migration mit dem oben genannten Inhalt zu generieren:

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

oder

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

wie in diesem Blogbeitrag erklärt .

Währungsformatierung

KÜSSEN Sie die zusätzlichen Bibliotheken zum Abschied und verwenden Sie integrierte Helfer. Verwendung number_to_currencyals @molf und @facundofarias vorgeschlagen.

Um mit dem number_to_currencyHelfer in der Rails-Konsole zu spielen, senden Sie einen Anruf an die Klasse von ActiveSupport's NumberHelper, um auf den Helfer zuzugreifen.

Zum Beispiel:

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

gibt die folgende Ausgabe

2500000,61

Überprüfen Sie den anderen Helfer optionsvon number_to_currency .

Wo soll ich es hinstellen?

Sie können es in einen Anwendungshelfer einfügen und für einen beliebigen Betrag in Ansichten verwenden.

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

Sie können es auch Itemals Instanzmethode in das Modell einfügen und dort aufrufen, wo Sie den Preis formatieren müssen (in Ansichten oder Hilfsprogrammen).

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

Und ein Beispiel, wie ich das number_to_currencyInnere eines Gegners verwende (beachten Sie die negative_formatOption, die zur Darstellung von Rückerstattungen verwendet wird)

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

Mithilfe von virtuellen Attributen (Link zu überarbeitetem (bezahltem) Railscast) können Sie Ihre price_in_cents in einer Ganzzahlspalte speichern und Ihrem Produktmodell ein virtuelles Attribut price_in_dollars als Getter und Setter hinzufügen.

# 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

Quelle: RailsCasts # 016: Virtuelle Attribute : Virtuelle Attribute sind eine saubere Möglichkeit, Formularfelder hinzuzufügen, die nicht direkt der Datenbank zugeordnet sind. Hier zeige ich, wie man mit Validierungen, Assoziationen und mehr umgeht.

Thomas Klemm
quelle
1
Dies lässt 200,0 eine Ziffer
Ajbraus
2

Auf jeden Fall ganze Zahlen .

Und obwohl BigDecimal technisch vorhanden 1.5ist, erhalten Sie dennoch einen reinen Float in Ruby.

strittig
quelle
2

Wenn jemand Sequel verwendet, sieht die Migration folgendermaßen aus:

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

irgendwie ignoriert Sequel: Präzision und: Skalierung

(Fortsetzung Version: Fortsetzung (3.39.0, 3.38.0))

Jethroo
quelle
2

Meine zugrunde liegenden APIs verwendeten alle Cent, um Geld darzustellen, und das wollte ich nicht ändern. Ich habe auch nicht mit großen Geldbeträgen gearbeitet. Also habe ich dies einfach in eine Hilfsmethode eingefügt:

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

Dadurch wird die Ganzzahl in eine Zeichenfolge mit mindestens drei Ziffern konvertiert (ggf. werden führende Nullen hinzugefügt). Anschließend wird vor den letzten beiden Ziffern ein Dezimalpunkt eingefügt, wobei niemals a verwendet wird Float. Von dort aus können Sie die für Ihren Anwendungsfall geeigneten Währungssymbole hinzufügen.

Es ist definitiv schnell und schmutzig, aber manchmal ist das in Ordnung!

Brent Royal-Gordon
quelle
Ich kann nicht glauben, dass dich niemand gestimmt hat. Dies war das einzige, was dazu beigetragen hat, mein Money-Objekt so in eine Form zu bringen, dass eine API es annehmen kann. (Dezimal)
Code-MonKy
2

Ich benutze es auf diese Weise:

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

Natürlich hängt das Währungssymbol, die Genauigkeit, das Format usw. von jeder Währung ab.

facundofarias
quelle
1

Sie können einige Optionen an number_to_currency(einen Standard-Rails 4-Ansichtshelfer) übergeben:

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

Wie von Dylan Markow gepostet

blnc
quelle
0

Einfacher Code für Ruby & Rails

<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50
Dinesh Vaitage
quelle