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?
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 BigDecimal
s 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
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
Product
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:
product.price = "$12.00"
se convertit automatiquement en classe Moneyproduct.price.to_s
affiche un nombre décimal formaté ("1234.00")product.price.format
affiche une chaîne correctement formatée pour la deviseproduct.price.cents.to_s
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.
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.
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.
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
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))
Certainement entiers .
Et même si BigDecimal existe techniquement, 1.5
vous donnera toujours un pur Float in Ruby.
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.
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!
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
Code simple pour Ruby & Rails
<%= number_to_currency(1234567890.50) %>
OUT PUT => $1,234,567,890.50
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.
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.
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.
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.
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