Money
Elixir library for working with Money safer, easier, and fun, is an interpretation of the Fowler's Money pattern in fun.prog.
"If I had a dime for every time I've seen someone use FLOAT to store currency, I'd have $999.997634" -- Bill Karwin
In short: You shouldn't represent monetary values by a float. Wherever
you need to represent money, use Money.
Documentation can be found at https://hexdocs.pm/money/Money.html on HexDocs
USAGE
five_eur = Money.new(500, :EUR) # %Money{amount: 500, currency: :EUR}
ten_eur = Money.add(five_eur, five_eur) # %Money{amount: 10_00, currency: :EUR}
hundred_eur = Money.multiply(ten_eur, 10) # %Money{amount: 100_00, currency: :EUR}
ninety_nine_eur = Money.subtract(hundred_eur, 1) # %Money{amount: 99_00, currency: :EUR}
shares = Money.divide(ninety_nine_eur, 2)
[%Money{amount: 50, currency: :EUR}, %Money{amount: 49, currency: :EUR}]
Money.equals?(five_eur, Money.new(500, :EUR)) # true
Money.zero?(five_eur); # false
Money.positive?(five_eur); # true
Money.Currency.symbol(:USD) # $
Money.Currency.symbol(Money.new(500, :AFN)) # ؋
Money.Currency.name(Money.new(500, :AFN)) # Afghani
Money.to_string(Money.new(500, :CNY)) # ¥ 5.00
Money.to_string(Money.new(1_234_56, :EUR), separator: ".", delimeter: ",", symbol: false)
"1.234,56"
Money.to_string(Money.new(1_234_56, :USD), fractional_unit: false) # "$1,234"Serialization to database with single currency
Bring Money to your Ecto project.
The underlying database type is integer
Set a default currency in
config.ex:config :money, default_currency: :USDCreate migration with integer type:
create table(:jobs) do add :amount, :integer endCreate schema using the
Money.Ecto.Amount.TypeEcto type (don't forget runmix ecto.migrate):schema "jobs" do field :amount, Money.Ecto.Amount.Type endSave to the database:
iex(1)> Repo.insert %Job{amount: Money.new(100, :USD)} [debug] QUERY OK db=90.7ms queue=0.1ms INSERT INTO "jobs" ("amount","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" [100, {{2019, 2, 12}, {7, 29, 8, 589489}}, {{2019, 2, 12}, {7, 29, 8, 593185}}] {:ok, %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, amount: %Money{amount: 100, currency: :USD}, id: 1, inserted_at: ~N[2019-02-12 07:29:08.589489], updated_at: ~N[2019-02-12 07:29:08.593185] }}Get from the database:
iex(2)> Repo.one(Job, limit: 1) [debug] QUERY OK source="jobs" db=1.8ms SELECT j0."id", j0."amount", j0."inserted_at", j0."updated_at" FROM "jobs" AS j0 [] %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, amount: %Money{amount: 100, currency: :USD}, id: 1, inserted_at: ~N[2019-02-12 07:29:08.589489], updated_at: ~N[2019-02-12 07:29:08.593185] }
Serialization to PostgreSQL with multiple currency
Money.Ecto.Composite.Type Ecto type represents serialization of Money.t to PostgreSQL Composite Types with saving currency.
Create migration with custom type:
def up do execute """ CREATE TYPE public.money_with_currency AS (amount integer, currency char(3)) """ end def down do execute """ DROP TYPE public.money_with_currency """ endThen use created custom type(
money_with_currency) for money field:def change do alter table(:jobs) do add :price, :money_with_currency end end`Create schema using the
Money.Ecto.Composite.TypeEcto type (don't forget runmix ecto.migrate):schema "jobs" do field :price, Money.Ecto.Composite.Type endSave to the database:
iex(1)> Repo.insert %Job{price: Money.new(100, :JPY)} [debug] QUERY OK db=7.7ms INSERT INTO "jobs" ("price","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" [{100, "JPY"}, {{2019, 2, 12}, {8, 7, 44, 729114}}, {{2019, 2, 12}, {8, 7, 44, 729124}}] {:ok, %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, id: 6, inserted_at: ~N[2019-02-12 08:07:44.729114], price: %Money{amount: 100, currency: :JPY}, updated_at: ~N[2019-02-12 08:07:44.729124] }}Get from the database:
iex(2)> Repo.one(Job, limit: 1) [debug] QUERY OK source="jobs" db=1.4ms SELECT j0."id", j0."price", j0."inserted_at", j0."updated_at" FROM "jobs" AS j0 [] %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, id: 6, inserted_at: ~N[2019-02-12 08:07:44.729114], price: %Money{amount: 100, currency: :JPY}, updated_at: ~N[2019-02-12 08:07:44.729124] }
Serialization to database (JSON) with multiple currency
Money.Ecto.Map.Type Ecto type represents serialization of Money.t to map(JSON) with saving currency.
Create migration with map type:
def change do alter table(:jobs) do add :price, :map end endCreate schema using the
Money.Ecto.Map.TypeEcto type (don't forget runmix ecto.migrate):schema "jobs" do field :price, Money.Ecto.map.Type endSave to the database:
iex(1)> Repo.insert %Job{price: Money.new(100, :JPY)} [debug] QUERY OK db=4.6ms INSERT INTO "jobs" ("price","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" [%{"amount" => 100, "currency" => "JPY"}, {{2019, 2, 26}, {9, 40, 14, 381721}}, {{2019, 2, 26}, {9, 40, 14, 381730}}] {:ok, %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, id: 9, inserted_at: ~N[2019-02-26 09:40:14.381721], price: %Money{amount: 100, currency: :JPY}, updated_at: ~N[2019-02-26 09:40:14.381730] }}Get from the database:
iex(8)> Repo.one(Job, limit: 1) [debug] QUERY OK source="jobs" db=2.0ms SELECT j0."id", j0."price", j0."inserted_at", j0."updated_at" FROM "jobs" AS j0 [] %MoneyTest.Offers.Job{ __meta__: #Ecto.Schema.Metadata<:loaded, "jobs">, id: 10, inserted_at: ~N[2019-02-26 09:40:45.205076], price: %Money{amount: 100, currency: :JPY}, updated_at: ~N[2019-02-26 09:40:45.205084] }
Money.Sigils
# Sigils for Money
import Money.Sigils
iex> ~M[1000]USD
%Money{amount: 1000, currency: :USD}
# If you have a default currency configured (e.g. to GBP), you can do
iex> ~M[1000]
%Money{amount: 1000, currency: :GBP}Money.Currency
# Currency convenience methods
import Money.Currency, only: [usd: 1, eur: 1, afn: 1]
iex> usd(100_00)
%Money{amount: 10000, currency: :USD}
iex> eur(100_00)
%Money{amount: 10000, currency: :EUR}
iex> afn(100_00)
%Money{amount: 10000, currency: :AFN}
Money.Currency.symbol(:USD) # $
Money.Currency.symbol(afn(500)) # ؋
Money.Currency.name(afn(500)) # Afghani
Money.Currency.get(:AFN) # %{name: "Afghani", symbol: "؋"}Phoenix.HTML.Safe
Bring Money to your Phoenix project.
If you are using Phoenix, you can include money objects directly into your output and they will be correctly escaped.
<b><%= Money.new(12345,67, :GBP) %></b>INSTALLATION
Money comes with no required dependencies.
Add the following to your mix.exs:
def deps do
[{:money, "~> 1.4"}]
end
then run mix deps.get.
CONFIGURATION
You can set a default currency and default formatting preferences as follows:
config :money,
default_currency: :EUR,
separator: ".",
delimeter: ",",
symbol: false,
symbol_on_right: false,
symbol_space: falseThen you don’t have to specify the currency.
iex> amount = Money.new(1_234_50)
%Money{amount: 123450, currency: :EUR}
iex> to_string(amount)
"1.234,50"Here is another example of formatting money:
iex> amount = Money.new(1_234_50)
%Money{amount: 123450, currency: :EUR}
iex> Money.to_string(amount, symbol: true, symbol_on_right: true, symbol_space: true)
"1.234,50 €"LICENSE
MIT License please see the LICENSE file.