¿Cuál es el mejor método para manejar moneda / dinero?

Estoy trabajando en un sistema de carrito de compras muy básico.

Tengo una tabla de items que tiene un price de columna de tipo integer .

Tengo problemas para mostrar el valor del precio en mis vistas de precios que incluyen tanto euros como centavos. ¿Me falta algo obvio en lo que se refiere al manejo de la moneda en el marco de Rails?

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 , que es ideal para el cálculo de precios.

Si insistes en usar números enteros, tendrás que convertir manualmente BigDecimal a BigDecimal cualquier lugar, lo que probablemente solo se convierta en un problema.

Como lo señala mcl, para imprimir el precio, use:

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

Aquí hay un enfoque fino y simple que aprovecha la composed_of (parte de ActiveRecord, usando el patrón ValueObject) y la gem Money

Necesitarás

  • The Money gem (versión 4.1.0)
  • Un modelo, por ejemplo Product
  • Una columna integer 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 ningún cambio adicional, 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 lo convertirán por usted. No es necesario agregar manejadores 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 formateado decimal (“1234.00”)
  • product.price.format muestra una cadena con el formato correcto para la moneda
  • Si necesita enviar centavos (a una pasarela de pago que quiere centavos), product.price.cents.to_s
  • Conversión de moneda gratis

La práctica común para manejar moneda es usar tipo decimal. Aquí hay un ejemplo simple de “Desarrollo web ágil con Rails”

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

Esto le permitirá manejar los precios de -999,999.99 a 999,999.99
Es posible que también 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 verificar la cordura de sus valores.

Usa la gem Money-rails . Maneja muy bien dinero y monedas en su modelo y también tiene un grupo de ayudantes para formatear sus precios.

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.

Si está utilizando Postgres (y como estamos en 2017 ahora), puede probar el tipo de columna :money .

 add_column :products, :price, :money, default: 0 

Definitivamente enteros .

Y a pesar de que BigDecimal técnicamente existe, 1.5 todavía te dará un Float puro en Ruby.

Si alguien está usando Sequel, la migración sería algo así como:

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

De alguna manera, Sequel ignora: precisión y: escala

(Versión 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 que el símbolo de moneda, la precisión, el formato, etc. dependen de cada moneda.

Puede pasar algunas opciones a number_to_currency (un ayudante de vista de Rails 4 estándar):

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

Según lo publicado por Dylan Markow

Mis API subyacentes usaban centavos para representar dinero, y no quería cambiar eso. Tampoco estaba trabajando con grandes cantidades de dinero. Así que simplemente puse esto en un método de ayuda:

 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, sin usar nunca un Float . Desde allí, puede agregar los símbolos de moneda apropiados para su caso de uso.

¡ Definitivamente es rápido y sucio, pero a veces eso está bien!

Código simple para Ruby & Rails

 < %= number_to_currency(1234567890.50) %> OUT PUT => $1,234,567,890.50