¿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?

Author: the Tin Man, 2009-06-20

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 BigDecimals 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
 466
Author: molf,
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
 108
Author: Ken Mayer,
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.

 25
Author: alex.zherdev,
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.

 7
Author: Troggy,
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.

 5
Author: Thomas Klemm,
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
 3
Author: The Whiz of Oz,
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.

 2
Author: moot,
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))

 2
Author: jethroo,
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.

 2
Author: facundofarias,
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

 1
Author: blnc,
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!

 1
Author: Brent Royal-Gordon,
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
 0
Author: Dinesh Vaitage,
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 como BigDecimal que resuelve muchos problemas.

  • precision y scale debe ajustarse, dependiendo de lo que está representando

    • Si trabaja con la recepción y el envío de pagos, precision: 8 y scale: 2 le da 999,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

Despídete de las bibliotecas adicionales y usa ayudantes integrados. Utilice number_to_currency como @molf y @facundofarias sugieren.

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 optionsde 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
 0
Author: Zlatko,
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