¿Cuál es el mejor método para manejar moneda / dinero?
-
06-07-2019 - |
Pregunta
Estoy trabajando en un sistema de carrito de compras muy básico.
Tengo una tabla items
que tiene una columna price
de tipo integer
.
Tengo problemas para mostrar el valor del precio en mis vistas para precios que incluyen euros y centavos. ¿Me estoy perdiendo algo obvio en lo que respecta al manejo de divisas en el marco de Rails?
Solución
Probablemente quiera usar un tipo DECIMAL
en su base de datos. En su migración, haga algo como esto:
# 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
En Rails, el tipo : decimal
se devuelve como BigDecimal
, lo cual es excelente para el cálculo de precios.
Si insiste en usar números enteros, tendrá que convertir manualmente desde y hacia BigDecimal
s en todas partes, lo que probablemente se convierta en un dolor.
Como señaló mcl, para imprimir el precio, use:
number_to_currency(price, :unit => "€")
#=> €1,234.01
Otros consejos
Aquí hay un enfoque simple y fino que aprovecha composite_of
(parte de ActiveRecord, usando el patrón ValueObject) y la gema Money
Necesitarás
- La Gema del dinero (versión 4.1.0)
- Un modelo, por ejemplo
Product
- Una columna
entera
en su modelo (y base de datos), por ejemplo:price
Escriba esto en su archivo product.rb
:
class Product > ActiveRecord::Base
composed_of :price,
:class_name => 'Money',
:mapping => %w(price cents),
:converter => Proc.new { |value| Money.new(value) }
# ...
Lo que obtendrás:
- Sin cambios adicionales, todos sus formularios mostrarán dólares y centavos, pero la representación interna sigue siendo solo centavos. Los formularios aceptarán valores como " $ 12,034.95 " y conviértelo por ti. No es necesario agregar controladores o atributos adicionales a su modelo, ni ayudantes desde su punto de vista.
-
product.price = " $ 12.00 "
se convierte automáticamente a la clase Money -
product.price.to_s
muestra un número con formato decimal (" 1234.00 ") -
product.price.format
muestra una cadena con el formato adecuado para la moneda - Si necesita enviar centavos (a una pasarela de pago que quiere centavos),
product.price.cents.to_s
- Conversión de divisas gratis
La práctica común para manejar la moneda es usar el tipo decimal. Aquí hay un ejemplo simple de " Desarrollo web ágil con rieles "
add_column :products, :price, :decimal, :precision => 8, :scale => 2
Esto le permitirá manejar precios desde -999,999.99 hasta 999,999.99
También es posible que desee incluir una validación en sus elementos como
def validate
errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
end
para comprobar la cordura de sus valores.
Utilice gema de dinero-rieles . Maneja muy bien el dinero y las monedas en su modelo y también tiene un montón de ayudantes para formatear sus precios.
Si está utilizando Postgres (y desde que estamos en 2017 ahora) es posible que desee probar su columna : money
. Pruebe
add_column :products, :price, :money, default: 0
Usando Atributos virtuales (Enlace a Railscast revisado (pagado)) puede almacenar su price_in_cents en un columna entera y agregue un atributo virtual price_in_dollars en su modelo de producto como getter y 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
Fuente: RailsCasts # 016: Atributos virtuales : Los atributos virtuales son una forma limpia de agregar campos de formulario que no se asignan directamente a la base de datos. Aquí muestro cómo manejar validaciones, asociaciones y más.
Definitivamente enteros .
Y a pesar de que BigDecimal técnicamente existe 1.5
todavía le dará un Float puro en Ruby.
Si alguien usa Sequel, la migración se vería así:
add_column :products, :price, "decimal(8,2)"
de alguna manera Sequel ignora: precisión y escala
(Versión de secuela: secuela (3.39.0, 3.38.0))
Lo estoy usando de esta manera:
number_to_currency(amount, unit: '€', precision: 2, format: "%u %n")
Por supuesto, el símbolo de la moneda, la precisión, el formato, etc. dependen de cada moneda.
Puede pasar algunas opciones a number_to_currency
(un asistente de visualización estándar de Rails 4):
number_to_currency(12.0, :precision => 2)
# => "$12.00"
Publicado por Dylan Markow
Todas mis API subyacentes usaban centavos para representar dinero, y no quería cambiar eso. Tampoco estaba trabajando con grandes cantidades de dinero. Así que acabo de poner esto en un método auxiliar:
sprintf("%03d", amount).insert(-3, ".")
Eso convierte el entero en una cadena con al menos tres dígitos (agregando ceros a la izquierda si es necesario), luego inserta un punto decimal antes de los dos últimos dígitos, nunca usando un Float
. Desde allí, puede agregar los símbolos de moneda que sean apropiados para su caso de uso.
Es definitivamente rápido y sucio, ¡pero a veces está bien!
Código simple para Ruby & amp; Rieles
<%= number_to_currency(1234567890.50) %>
OUT PUT => $1,234,567,890.50
Solo una pequeña actualización y una cohesión de todas las respuestas para algunos aspirantes a junior / principiantes en el desarrollo de RoR que seguramente vendrán aquí para algunas explicaciones.
Trabajando con dinero
Use : decimal
para almacenar dinero en la base de datos, como sugirió @molf (y lo que mi compañía usa como estándar dorado cuando trabaja con dinero).
# 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
Pocos puntos:
: decimal
se utilizará comoBigDecimal
, lo que resuelve muchos problemas.precisión
yscale
deben ajustarse, dependiendo de lo que esté representando-
Si trabaja con la recepción y el envío de pagos,
precisión: 8
yscale: 2
le da999,999.99
como la cantidad más alta , lo cual está bien en el 90% de los casos. -
Si necesita representar el valor de una propiedad o un automóvil raro, debe usar una
precision
. más alta
-
Si trabaja con coordenadas (longitud y latitud), seguramente necesitará un
scale
. más alto
-
Cómo generar una migración
Para generar la migración con el contenido anterior, ejecute en la terminal:
bin/rails g migration AddPriceToItems price:decimal{8-2}
o
bin/rails g migration AddPriceToItems 'price:decimal{5,2}'
como se explica en este blog publicación.
Formato de moneda
KISS las bibliotecas adicionales se despiden y usan ayudantes integrados. Utilice number_to_currency
como sugirieron @molf y @facundofarias.
Para jugar con el asistente number_to_currency
en la consola de Rails, envíe una llamada a la clase ActiveSupport
de NumberHelper
para acceder al asistente.
Por ejemplo:
ActiveSupport::NumberHelper.number_to_currency(2_500_000.61, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
da el siguiente resultado
2500000,61€
Verifique las otras opciones
de number_to_currency ayudante.
Dónde ponerlo
Puede ponerlo en una aplicación auxiliar y usarlo dentro de las vistas por cualquier cantidad.
module ApplicationHelper
def format_currency(amount)
number_to_currency(amount, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
end
end
O puede ponerlo en el modelo Item
como método de instancia y llamarlo donde necesite formatear el precio (en vistas o ayudantes).
class Item < ActiveRecord::Base
def format_price
number_to_currency(price, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
end
end
Y, un ejemplo de cómo uso el number_to_currency
dentro de un controlador (observe la opción negative_format
, utilizada para representar reembolsos)
def refund_information
amount_formatted =
ActionController::Base.helpers.number_to_currency(@refund.amount, negative_format: '(%u%n)')
{
# ...
amount_formatted: amount_formatted,
# ...
}
end