image by Dan Dennis
Don't Use Floats and Use the Ruby Money Gem to Represent Currencies
We often have to manage monetary values in our applications. Guess what? There’s a terrific open source solution in the Ruby community that is much better than rolling your own.
Instead of…
…using Float
s or BigDecimal
s to represent money:
# In your migration
add_column :products, :price, :decimal
class Product < ApplicationModel
def price_to_s
"$ #{price.round(2).to_s("F")} USD"
end
end
product.price = 5
#=> 0.5e1
product.price_to_s
#=> "$ 5.0 USD"
Use…
…the Ruby money
gem, or in Rails, the money-rails
gem:
# In your migration
add_monetize :products, :price
class Product < ApplicationModel
monetize :price_cents
end
product.price = Money.from_amount(5, "USD")
#=> #<Money fractional:500 currency:USD>
product.price.format
#=> "$5.00"
Why?
Firstly, never use a floating point implementation to represent your currency values. Floats can introduce rounding errors due to their underlying representaion inside a computer. Just do not do this.
Decimals solve that issue to an extent as they behave in a more intuitive (and money-like) manner.
The money
gem provides additional benefits as well as enforcing the well-worn recommendations for underlying data structures. It provides a simple, yet sophisticated, battle-tested, “value object” pattern—like all the best gems—for monetary amounts and currencies.
It has a sophisticated implementation of #format
that works with the standard Ruby internationalisation (i18n) backend to properly (and flexibly) display monetary amounts as strings.
You can also easily convert currencies using the built-in currency exchange functionality, for which you can provide your own rates or link to a number of regularly updated currency rate services.
Why not?
You might want to code your own your ‘money’ representation if your application is operating in a more detailed financial modelling world. If you are calculating fractional monetary amounts, the money
gem may not have the levels of sophistication you require.
Last updated on November 15th, 2021