web-dev-qa-db-fra.com

Quelle est la meilleure méthode de traitement des devises/de l’argent?

Je travaille sur un système de panier d'achat très basique. 

J'ai une table items qui a une colonne price de type integer

Je ne parviens pas à afficher la valeur du prix dans mes vues pour des prix comprenant à la fois des euros et des centimes d'euro. Me manque-t-il quelque chose d'évident en ce qui concerne la gestion de la monnaie dans le cadre de Rails?

308
Barry Gallagher

Vous voudrez probablement utiliser un type DECIMAL dans votre base de données. Dans votre migration, faites quelque chose comme ça:

# 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

Dans Rails, le type :decimal est renvoyé sous la forme BigDecimal, ce qui est excellent pour le calcul du prix.

Si vous insistez pour utiliser des entiers, vous devrez convertir manuellement vers et depuis BigDecimals partout, ce qui ne fera probablement que devenir un problème.

Comme le souligne mcl, pour imprimer le prix, utilisez:

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

Voici une approche simple et raffinée qui exploite composed_of (composant d’ActiveRecord, utilisant le modèle ValueObject) et la gemme Money. 

Tu auras besoin

  • Le bijou d’argent (version 4.1.0) 
  • Un modèle, par exemple Product
  • Une colonne integer dans votre modèle (et votre base de données), par exemple :price

Ecrivez ceci dans votre fichier product.rb:

class Product > ActiveRecord::Base

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

Qu'est-ce que vous obtiendrez:

  • Sans aucun changement supplémentaire, tous vos formulaires indiqueront des dollars et des cents, mais la représentation interne ne représente toujours que des cents. Les formulaires accepteront des valeurs telles que "12 034,95 $" et le convertiront pour vous. Il n'est pas nécessaire d'ajouter des gestionnaires ou des attributs supplémentaires à votre modèle, ni des aides dans votre vue.
  • product.price = "$12.00" se convertit automatiquement en classe Money
  • product.price.to_s affiche un nombre décimal formaté ("1234.00")
  • product.price.format affiche une chaîne correctement formatée pour la devise
  • Si vous devez envoyer des cents (à une passerelle de paiement qui veut des sous), product.price.cents.to_s
  • Conversion de devise gratuite
111
Ken Mayer

La pratique courante en matière de traitement des devises consiste à utiliser le type décimal ..__ Voici un exemple simple tiré de "Développement Web agile avec Rails"

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

Cela vous permettra de gérer des prix allant de -999 999,99 à 999 999,99.
Vous pouvez également inclure une validation dans vos éléments tels que 

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

pour vérifier ses valeurs.

25
alex.zherdev

Utilisez money-Rails gem . Il gère bien l’argent et les devises dans votre modèle et dispose également d’un ensemble d’aides pour formater vos prix.

7
Troggy

Utilisation de Attributs virtuels (Lien vers Railscast révisé (payant)) vous pouvez stocker votre price_in_cents dans une colonne d’entier et ajouter un attribut virtuel price_in_dollars dans votre modèle de produit en tant que getter et 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: Attributs virtuels : Les attributs virtuels sont un moyen propre d’ajouter des champs de formulaire qui ne mappent pas directement à la base de données. Ici, je montre comment gérer les validations, les associations et plus.

5
Thomas Klemm

Si vous utilisez Postgres (et que nous sommes en 2017 maintenant), vous pouvez essayer leur type de colonne :money.

add_column :products, :price, :money, default: 0
5
The Whiz of Oz

Si quelqu'un utilise Sequel, la migration ressemblerait à quelque chose comme: 

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

en quelque sorte Sequel ignore: precision et: scale

(Version de la suite: suite (3.39.0, 3.38.0))

2
jethroo

Certainement entiers .

Et même si BigDecimal existe techniquement, 1.5 vous donnera toujours un pur Float in Ruby.

2
moot

Je l'utilise de cette façon:

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

Bien entendu, le symbole monétaire, la précision, le format, etc., dépendent de chaque devise.

2
facundofarias

Mes API sous-jacentes utilisaient toutes des centimes pour représenter de l'argent, et je ne voulais pas changer cela. Je ne travaillais pas non plus avec de grosses sommes d'argent. Donc, je viens de mettre cela dans une méthode d'assistance:

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

Cela convertit l'entier en une chaîne d'au moins trois chiffres (ajoutant des zéros à gauche si nécessaire), puis insère un point décimal avant les deux derniers chiffres, sans jamais utiliser un Float. À partir de là, vous pouvez ajouter les symboles de devise appropriés à votre cas d'utilisation.

C'est définitivement rapide et sale, mais parfois c'est très bien!

1
Brent Royal-Gordon

Vous pouvez transmettre certaines options à number_to_currency (un assistant de vue Rails 4 standard):

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

Tel que publié par Dylan Markow

1
blnc

Code simple pour Ruby & Rails

<%= number_to_currency(1234567890.50) %>

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

Juste une petite mise à jour et une cohésion de toutes les réponses pour quelques aspirants débutants/débutants dans le développement de RoR qui viendront sûrement ici pour quelques explications.

Travailler avec de l'argent

Utilisez :decimal pour stocker de l’argent dans la base de données, comme @molf l’a suggéré (et ce que mon entreprise utilise comme norme de référence pour travailler avec de l’argent).

# 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

Quelques points:

  • :decimal sera utilisé comme BigDecimal, ce qui résoudra de nombreux problèmes.

  • precision et scale devraient être ajustés, en fonction de ce que vous représentez

    • Si vous travaillez avec la réception et l’envoi de paiements, precision: 8 et scale: 2 vous attribuent 999,999.99 au montant le plus élevé, ce qui est acceptable dans 90% des cas.

    • Si vous devez représenter la valeur d'une propriété ou d'une voiture rare, vous devez utiliser une variable precision supérieure.

    • Si vous travaillez avec des coordonnées (longitude et latitude), vous aurez sûrement besoin d'une scale plus élevée.

Comment générer une migration

Pour générer la migration avec le contenu ci-dessus, exécutez in terminal:

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

ou

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

comme expliqué dans ce blog post.

Format de devise

KISS les bibliothèques supplémentaires adieu et utilisez les helpers intégrés. Utilisez number_to_currency comme @molf et @facundofarias sont suggérés.

Pour jouer avec l'assistant number_to_currency dans la console Rails, envoyez un appel à la classe ActiveSupport de NumberHelper afin de pouvoir y accéder.

Par exemple:

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

donne la sortie suivante

2500000,61€

Vérifiez l'autre options de number_to_currency helper.

Où le mettre

Vous pouvez le placer dans un assistant d’application et l’utiliser dans toutes les vues. 

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

Ou vous pouvez le placer dans le modèle Item en tant que méthode d'instance et appelez-le là où vous avez besoin de formater le prix (en vues ou en aides).

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

Et, un exemple d'utilisation de number_to_currency à l'intérieur d'un contrôleur (notez l'option negative_format, utilisée pour représenter les remboursements)

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