¿Cuál es el mejor método de manejo de moneda/dinero?
Estoy trabajando en un sistema de carrito de compras muy básico.
Tengo una tabla items
que tiene una columna price
de tipo integer
.
Estoy teniendo problemas para mostrar el valor del precio en mis vistas para los precios que incluyen tanto euros como centavos. ¿Me estoy perdiendo algo obvio en cuanto al manejo de moneda en el marco de Rails se refiere?
13 answers
Probablemente quieras usar un tipo DECIMAL
en tu 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 que es ideal para el cálculo de precios.
Si insiste en usar enteros, tendrá que convertir manualmente hacia y desde BigDecimal
s en todas partes, lo que probablemente se convertirá en un dolor.
Como señaló mcl, para imprimir el precio, use:
number_to_currency(price, :unit => "€")
#=> €1,234.01
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-28 12:35:14
Aquí hay un enfoque fino y simple que aprovecha composed_of
(parte de ActiveRecord, utilizando el patrón ValueObject) y la gema de Dinero
Necesitarás
- La gema del dinero (versión 4.1.0)
- Un modelo, por ejemplo
Product
- Una columna
integer
en su modelo (y base de datos), por ejemplo:price
Escribe esto en tu 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 ningún cambio adicional, todos sus formularios se mostrar dólares y centavos, pero la representación interna sigue siendo solo centavos. Los formularios aceptarán valores como" $12,034.95 " y lo convertirán por usted. No es necesario agregar controladores o atributos adicionales a su modelo, o ayudantes en su 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 un pago puerta de enlace que quiere centavos),
product.price.cents.to_s
- Conversión de moneda gratis
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-17 15:59:26
La práctica común para manejar la moneda es usar el tipo decimal. He aquí un ejemplo sencillo de "Agile Web Development with Rails"
add_column :products, :price, :decimal, :precision => 8, :scale => 2
Esto le permitirá manejar precios de -999,999.99 a 999,999.99
También puede 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
To sanity-compruebe sus valores.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-19 20:48:39
Use gema money-rails. Maneja muy bien el dinero y las monedas en su modelo y también tiene un montón de ayudantes para formatear sus precios.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 00:38:26
Usando Atributos virtuales (Enlace a Railscast revisado(pagado)) puede almacenar su price_in_cents en una columna entera y agregar 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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-22 21:08:45
Si está utilizando Postgres (y ya que estamos en 2017 ahora) es posible que desee darle una oportunidad a su tipo de columna :money
.
add_column :products, :price, :money, default: 0
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 16:35:26
Definitivamente enteros.
Y aunque BigDecimal técnicamente existe 1.5
todavía te dará un flotador puro en Ruby.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-22 20:52:19
Si alguien está usando Sequel, la migración se vería algo como:
add_column :products, :price, "decimal(8,2)"
De alguna manera Secuela ignora: precisión y: escala
(Sequel Version: sequel (3.39.0, 3.38.0))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-17 10:35:51
Lo estoy usando de esta manera:
number_to_currency(amount, unit: '€', precision: 2, format: "%u %n")
Por supuesto que el símbolo de la moneda, la precisión, el formato y así sucesivamente depende de cada moneda.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-17 17:34:18
Puede pasar algunas opciones a number_to_currency
(un ayudante de vista estándar de Rails 4):
number_to_currency(12.0, :precision => 2)
# => "$12.00"
Según lo publicado por Dylan Markow
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:38
Mis API subyacentes estaban usando 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 de ayuda:
sprintf("%03d", amount).insert(-3, ".")
Que convierte el entero en una cadena con al menos tres dígitos (agregando ceros iniciales si es necesario), luego inserta un punto decimal antes de los últimos dos dígitos, nunca usando un Float
. Desde allí puede agregar cualquier símbolo de moneda que sea apropiado para su caso de uso.
Es definitivamente rápido y sucio, pero a veces eso está bien!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-10 01:59:56
Código simple para Ruby & Rails
<%= number_to_currency(1234567890.50) %>
OUT PUT => $1,234,567,890.50
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-30 08:37:51
Solo una pequeña actualización y una cohesión de todas las respuestas para algunos aspirantes a juniors/principiantes en 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 empresa usa como un estándar de oro cuando se 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
Algunos puntos:
:decimal
se va a usar comoBigDecimal
que resuelve muchos problemas.-
precision
yscale
debe ajustarse, dependiendo de lo que está representandoSi trabaja con la recepción y el envío de pagos,
precision: 8
yscale: 2
le da999,999.99
como la cantidad más alta, que está bien en el 90% de los casos.Si necesita representar el valor de una propiedad o un automóvil raro, debe usar un
precision
superior.Si trabaja con coordenadas (longitud y latitud), seguramente necesitará una mayor
scale
.
Cómo generar una migración
Para generar la migración con el contenido anterior, ejecute en 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 post.
Formato de moneda
Para jugar con number_to_currency
helper en Rails console, envíe una llamada a los ActiveSupport
' s NumberHelper
para acceder al helper.
Por ejemplo:
ActiveSupport::NumberHelper.number_to_currency(2_500_000.61, unit: '€', precision: 2, separator: ',', delimiter: '', format: "%n%u")
Da la siguiente salida
2500000,61€
Compruebe el otro options
de number_to_currency helper.
Dónde ponerlo
Puede ponerlo en un ayudante de aplicación y usarlo dentro de vistas para 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 un método de instancia, y llamarlo donde necesita formatear el precio (en vistas o ayudante).
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 contrroler (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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-26 14:09:13