Qual è il metodo migliore per gestire valuta / denaro?


323

Sto lavorando a un sistema di carrello della spesa molto semplice.

Ho una tabella itemsche ha una colonna pricedi tipo integer.

Ho problemi a visualizzare il valore del prezzo nelle mie visualizzazioni per i prezzi che includono sia Euro che centesimi. Mi sto perdendo qualcosa di ovvio per quanto riguarda la gestione della valuta nel framework Rails?


se qualcuno usa sql, allora DECIMAL(19, 4) è una scelta popolare controlla questo controlla anche qui i formati di valuta mondiale per decidere quanti posti decimali usare, la speranza aiuta.
shaijut,

Risposte:


495

Probabilmente vorrai usare un DECIMALtipo nel tuo database. Nella tua migrazione, fai qualcosa del genere:

# 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, il :decimaltipo viene restituito come BigDecimal, il che è ottimo per il calcolo del prezzo.

Se insisti nell'utilizzare numeri interi, dovrai convertire manualmente da e verso BigDecimals ovunque, il che probabilmente diventerà solo una seccatura.

Come sottolineato da mcl, per stampare il prezzo, utilizzare:

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

13
Utilizza l'helper number_to_currency, maggiori informazioni su api.rubyonrails.org/classes/ActionView/Helpers/…
mlibby

48
In realtà, è molto più sicuro e più facile usare un numero intero in combinazione con act_as_dollars. Sei mai stato morso dal confronto in virgola mobile? In caso contrario, non rendere questa la tua prima esperienza. :) Con act_as_dollars, metti le cose in formato 12.34, è memorizzato come 1234 ed esce come 12.34.
Sarah Mei,

50
@Sarah Mei: BigDecimals + formato decimale della colonna evita proprio questo.
molf,

114
È importante non copiare semplicemente questa risposta alla cieca: la precisione 8, scala 2 fornisce un valore massimo di 999.999,99 . Se hai bisogno di un numero maggiore di un milione, aumenta la precisione!
Jon Cairns,

22
È anche importante non usare ciecamente una scala 2 se stai gestendo valute diverse: alcune valute nordafricane e arabe come l'Oman Rial o il Dinar tunisino hanno una scala 3, quindi la precisione 8 scala 3 è più appropriata lì .
Battere Richartz il

117

Ecco un approccio semplice e raffinato che sfrutta composed_of(parte di ActiveRecord, usando il modello ValueObject) e la gemma Money

Avrai bisogno

  • The Money gem (versione 4.1.0)
  • Un modello, per esempio Product
  • Una integercolonna nel modello (e nel database), ad esempio:price

Scrivi questo nel tuo product.rbfile:

class Product > ActiveRecord::Base

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

Cosa otterrai:

  • Senza ulteriori modifiche, tutti i moduli mostreranno dollari e centesimi, ma la rappresentazione interna è ancora solo centesimi. I moduli accettano valori come "$ 12.034,95" e li convertono per te. Non è necessario aggiungere ulteriori gestori o attributi al modello o aiutanti nella vista.
  • product.price = "$12.00" si converte automaticamente nella classe Money
  • product.price.to_s visualizza un numero in formato decimale ("1234.00")
  • product.price.format visualizza una stringa correttamente formattata per la valuta
  • Se devi inviare centesimi (a un gateway di pagamento che desidera centesimi), product.price.cents.to_s
  • Conversione di valuta gratis

14
Adoro questo approccio. Nota però: assicurati che la tua migrazione per "prezzo" in questo esempio non consenta valori nulli e predefiniti a 0 per non impazzire nel tentativo di capire perché non funziona.
Cory,

3
Ho trovato la gemma money_column (estratta da Shopify) per essere molto semplice da usare ... più facile della gemma denaro, se non hai bisogno di conversione di valuta.
Talirico,

7
Dovrebbe essere notato per tutti coloro che usano la gemma Money che il core team di Rails sta discutendo di deprecare e rimuovere "compound_of" dal framework. Ho il sospetto che la gemma verrà aggiornata per gestirlo se dovesse accadere, ma se stai guardando Rails 4.0 dovresti essere consapevole di questa possibilità
Peer Allan,

1
Per quanto riguarda il commento di @ PeerAllan sulla rimozione di composed_of qui è più dettagliato su questo e un'implementazione alternativa.
HerbCSO,

3
Inoltre, è davvero difficile usare la gemma rails-money .
fotanus,

25

La pratica comune per la gestione della valuta è l'uso del tipo decimale. Ecco un semplice esempio da "Sviluppo Web agile con Rails"

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

Ciò ti consentirà di gestire prezzi da -999.999,99 a 999.999,99
Puoi anche includere una convalida nei tuoi articoli come

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

per verificare la sanità mentale dei tuoi valori.


1
Questa soluzione consente inoltre di utilizzare SQL Sum e amici.
Larry K,

4
Potresti eventualmente fare: convalida: prezzo,: presenza => vero,: numericità => {: maggiore_hanno => 0}
Galaxy

9

Se stai usando Postgres (e dal momento che siamo nel 2017 ora) potresti provare il loro :moneytipo di colonna.

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

7

Usa gemma binario . Gestisce bene denaro e valute nel tuo modello e ha anche un sacco di aiutanti per formattare i tuoi prezzi.


Sì, sono d'accordo con questo. Generalmente, gestisco il denaro memorizzandolo come centesimi (intero) e usando una gemma come atti-come-denaro o denaro (binari del denaro) per gestire i dati in memoria. Gestirlo in numeri interi previene quegli errori di arrotondamento sgradevoli. Ad esempio 0,2 * 3 => 0,600000000000000001 Questo, ovviamente, funziona solo se non è necessario gestire frazioni di un centesimo.
Chad M

Questo è molto bello se stai usando le guide. Inseriscilo e non preoccuparti dei problemi con una colonna decimale. Se si utilizza questa vista, questa risposta può essere utile pure: stackoverflow.com/questions/18898947/...
mooreds

6

Solo un piccolo aggiornamento e una coesione di tutte le risposte per alcuni aspiranti junior / principianti nello sviluppo del RoR che verranno sicuramente qui per alcune spiegazioni.

Lavorare con i soldi

Utilizzalo :decimalper archiviare denaro nel DB, come suggerito da @molf (e ciò che la mia azienda utilizza come standard aureo quando lavora con denaro).

# 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

Pochi punti:

  • :decimalverrà utilizzato come ciò BigDecimalche risolve molti problemi.

  • precisione scaledovrebbe essere regolato, a seconda di ciò che stai rappresentando

    • Se lavori con la ricezione e l'invio di pagamenti precision: 8e scale: 2ti offre 999,999.99l'importo più elevato, il che va bene nel 90% dei casi.

    • Se devi rappresentare il valore di una proprietà o di un'auto rara, dovresti utilizzare un valore superiore precision.

    • Se lavori con le coordinate (longitudine e latitudine), avrai sicuramente bisogno di un valore superiore scale .

Come generare una migrazione

Per generare la migrazione con il contenuto sopra, esegui nel terminale:

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

o

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

come spiegato in questo post sul blog .

Formattazione della valuta

BACIO addio alle biblioteche extra e usa gli helper integrati. Utilizzare number_to_currencycome suggerito da @molf e @facundofarias.

Per giocare con l' number_to_currencyhelper nella console di Rails, invia una chiamata alActiveSupport 's NumberHelperdi classe al fine di accedere al aiutante.

Per esempio:

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

fornisce il seguente output

2500000,61

Controlla l'altro optionsdi number_to_currency aiutante.

Dove metterlo

È possibile inserirlo in un supporto dell'applicazione e utilizzarlo all'interno delle viste per qualsiasi importo.

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

Oppure puoi inserirlo nel Itemmodello come metodo di istanza e chiamarlo dove è necessario formattare il prezzo (in viste o helper).

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

E, un esempio di come uso l' number_to_currencyinterno di un dispositivo di controllo (nota l' negative_formatopzione, utilizzata per rappresentare i rimborsi)

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

5

Usando gli Attributi Virtuali (Link al Railscast rivisto (a pagamento) puoi memorizzare i tuoi price_in_cents in una colonna intera e aggiungere un attributo virtuale price_in_dollars nel tuo modello di prodotto come getter e 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

Fonte: RailsCasts # 016: Attributi virtuali : gli attributi virtuali sono un modo semplice per aggiungere campi modulo che non si associano direttamente al database. Qui mostro come gestire convalide, associazioni e altro.


1
questo lascia 200,0 una cifra
ajbraus il


2

Se qualcuno utilizza Sequel la migrazione sarebbe simile a:

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

in qualche modo Sequel ignora: precisione e: scala

(Versione sequel: sequel (3.39.0, 3.38.0))


2

Le mie API sottostanti utilizzavano tutti i centesimi per rappresentare il denaro e non volevo cambiarlo. Né stavo lavorando con grandi quantità di denaro. Quindi ho appena inserito questo in un metodo di supporto:

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

Che converte l'intero in una stringa con almeno tre cifre (aggiungendo zeri iniziali se necessario), quindi inserisce un punto decimale prima delle ultime due cifre, senza mai usare un Float . Da lì puoi aggiungere qualsiasi simbolo di valuta appropriato per il tuo caso d'uso.

È sicuramente veloce e sporco, ma a volte va bene!


Non posso credere che nessuno ti abbia votato. Questa è stata l'unica cosa che ha funzionato per ottenere il mio oggetto Money in una forma tale che un'API possa prenderlo. (Decimale)
Code-MonKy

2

Lo sto usando in questo modo:

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

Ovviamente il simbolo della valuta, la precisione, il formato e così via dipendono da ciascuna valuta.


1

Puoi passare alcune opzioni a number_to_currency(un aiutante di visualizzazione standard di Rails 4):

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

Come pubblicato da Dylan Markow


0

Codice semplice per Ruby & Rails

<%= number_to_currency(1234567890.50) %>

OUT PUT => $1,234,567,890.50
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.