Multi-currency support: Money + Currency class improvements #553
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR is a precursor to #543, which was growing too big and tackling too many things at once.
Overview
Below are some explanations of changes and sample usage for documentation purposes.
Money lib
Adds
Money
,Money::Currency
, andMoney::Arithmetic
tolib
as these represent generic concepts that could apply to any app that deals with money.Money
packages up all the information required to represent a monetary value (amount, currency)Money::Currency
stores information about all global currencies and can provide a list of these throughMoney::Currency.all
orMoney::Currency.popular
Money::Arithmetic
allows us to add, subtract, multiply, divide, and compare monetary values without any special syntax. In other words, we can doMoney.new(0) == Money.new(0)
orMoney.new(100) > Money.new(5)
Why not
money
andmoney-rails
?There are two main reasons I decided to go with a custom implementation:
money
andmoney-rails
gems are somewhat opinionated about a project's money storage strategy. Throughout most of its API, it assumes that money is being stored in minor currency units. Our project does not do that. We store inDecimal{19,4}
, which was chosen to make the data a lot more intuitive (1 less layer of indirection). Furthermore, themoney-rails
gem integrates at the model level (with migration helpers), which as we saw with a first attempt at using it, caused unnecessary confusion for new contributors.Monetizable Concern + Form Helper
In an attempt to keep things simple and intuitive, as an alternative to the
money-rails
strategy that "magically" turns fields likeAccount.balance
intoMoney
instances, I've created aMonetizable
concern with a straightforward API:This unobtrusively adds a field called
balance_money
, which returnsMoney.new(balance, currency)
.Furthermore, I've created a form helper with the following API:
f.money_field
will readamount_money
and create an:amount
and:currency
input that is updated on submission.The overall goal here is to give the developer flexibility when working with money and making it explicit when a field deals with money and when it doesn't.