Skip to content

Commit be4e29c

Browse files
committed
Propogate money format_options through all operations
1 parent 99c8e16 commit be4e29c

17 files changed

Lines changed: 351 additions & 256 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This is the changelog for Money v5.5.0 released on ______, 2021. For older chan
66

77
* Adds format options to `t:Money` to allow per-money formatting options to be applied with the `String.Chars` protocol. Thanks to @morinap for the feature request.
88

9+
* Adds `Money.put_format_options/2`
10+
911
# Changelog for Money v5.4.1
1012

1113
This is the changelog for Money v5.4.1 released on January 7th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/kipcole9/money/tags)

config/dev.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ config :ex_money,
1414
default_cldr_backend: Money.Cldr
1515

1616
config :ex_cldr,
17-
default_backend: Money.Cldr
17+
default_backend: Money.Cldr

config/test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@ config :ex_money,
1616
log_failure: nil,
1717
log_info: nil,
1818
default_cldr_backend: Test.Cldr
19-

lib/money.ex

Lines changed: 104 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ defmodule Money do
197197
case validate_currency(currency_code) do
198198
{:error, {_exception, message}} ->
199199
{:error, {Money.UnknownCurrencyError, message}}
200+
200201
{:ok, code} ->
201202
format_options = extract_format_options(options)
202203
%Money{amount: amount, currency: code, format_options: format_options}
@@ -326,6 +327,9 @@ defmodule Money do
326327
327328
* `amount` is a float
328329
330+
* `options` is a keyword list of options passed
331+
to `Money.new/3`. The default is `[]`.
332+
329333
## Examples
330334
331335
iex> Money.from_float 1.23456, :USD
@@ -339,15 +343,17 @@ defmodule Money do
339343
"Reduce the precision or call Money.new/2 with a Decimal or String amount"}}
340344
341345
"""
342-
Cldr.Macros.doc_since "2.0.0"
346+
Cldr.Macros.doc_since("2.0.0")
343347
@max_precision_allowed 15
344-
@spec from_float(float | currency_code, float | currency_code) ::
348+
@spec from_float(float | currency_code, float | currency_code, Keyword.t()) ::
345349
Money.t() | {:error, {module(), String.t()}}
346350

347-
def from_float(currency_code, amount)
351+
def from_float(currency_code, amount, options \\ [])
352+
353+
def from_float(currency_code, amount, options)
348354
when (is_binary(currency_code) or is_atom(currency_code)) and is_float(amount) do
349355
if Cldr.Number.precision(amount) <= @max_precision_allowed do
350-
new(currency_code, Decimal.from_float(amount))
356+
new(currency_code, Decimal.from_float(amount), options)
351357
else
352358
{:error,
353359
{Money.InvalidAmountError,
@@ -358,9 +364,9 @@ defmodule Money do
358364
end
359365
end
360366

361-
def from_float(amount, currency_code)
367+
def from_float(amount, currency_code, options)
362368
when (is_binary(currency_code) or is_atom(currency_code)) and is_float(amount) do
363-
from_float(currency_code, amount)
369+
from_float(currency_code, amount, options)
364370
end
365371

366372
@doc """
@@ -379,6 +385,9 @@ defmodule Money do
379385
380386
* `amount` is a float
381387
388+
* `options` is a keyword list of options passed
389+
to `Money.new/3`. The default is `[]`.
390+
382391
## Examples
383392
384393
iex> Money.from_float!(:USD, 1.234)
@@ -389,16 +398,36 @@ defmodule Money do
389398
(ex_money) lib/money.ex:293: Money.from_float!/2
390399
391400
"""
392-
Cldr.Macros.doc_since "2.0.0"
393-
@spec from_float!(currency_code, float) :: Money.t() | no_return()
401+
Cldr.Macros.doc_since("2.0.0")
402+
@spec from_float!(currency_code, float, Keyword.t()) :: Money.t() | no_return()
394403

395-
def from_float!(currency_code, amount) do
396-
case from_float(currency_code, amount) do
404+
def from_float!(currency_code, amount, options \\ []) do
405+
case from_float(currency_code, amount, options) do
397406
{:error, {exception, reason}} -> raise exception, reason
398407
money -> money
399408
end
400409
end
401410

411+
@doc """
412+
Add format options to a `t:Money`.
413+
414+
## Arguments
415+
416+
* `money` is any valid `t:Money` type returned
417+
by `Money.new/2`
418+
419+
* `options` is a keyword list of options. These
420+
options are used when calling `Money.to_string/2`.
421+
The default is `[]`
422+
423+
"""
424+
425+
Cldr.Macros.doc_since("5.5.0")
426+
@spec put_format_options(Money.t(), Keyword.t()) :: Money.t()
427+
def put_format_options(%Money{} = money, options) when is_list(options) do
428+
%{money | format_options: options}
429+
end
430+
402431
@doc """
403432
Parse a string and return a `Money.t` or an error.
404433
@@ -516,7 +545,7 @@ defmodule Money do
516545
{:error, {Money.ParseError, "Could not parse \\"USD 100 with trailing text\\"."}}
517546
518547
"""
519-
Cldr.Macros.doc_since "3.2.0"
548+
Cldr.Macros.doc_since("3.2.0")
520549
@spec parse(String.t(), Keyword.t()) :: Money.t() | {:error, {module(), String.t()}}
521550

522551
def parse(string, options \\ []) do
@@ -672,6 +701,7 @@ defmodule Money do
672701

673702
def to_string(%Money{} = money, options) when is_list(options) do
674703
default_options = [backend: Money.default_backend(), currency: money.currency]
704+
675705
options =
676706
default_options
677707
|> Keyword.merge(money.format_options)
@@ -814,11 +844,12 @@ defmodule Money do
814844
"""
815845
@spec add(money_1 :: Money.t(), money_2 :: Money.t()) ::
816846
{:ok, Money.t()} | {:error, {module(), String.t()}}
817-
def add(%Money{currency: same_currency, amount: amount_a}, %Money{
818-
currency: same_currency,
819-
amount: amount_b
820-
}) do
821-
{:ok, %Money{currency: same_currency, amount: Decimal.add(amount_a, amount_b)}}
847+
848+
def add(
849+
%Money{currency: same_currency, amount: amount_a},
850+
%Money{currency: same_currency, amount: amount_b} = money_b
851+
) do
852+
{:ok, %{money_b | amount: Decimal.add(amount_a, amount_b)}}
822853
end
823854

824855
def add(%Money{currency: code_a}, %Money{currency: code_b}) do
@@ -885,11 +916,10 @@ defmodule Money do
885916
@spec sub(money_1 :: Money.t(), money_2 :: Money.t()) ::
886917
{:ok, Money.t()} | {:error, {module(), String.t()}}
887918

888-
def sub(%Money{currency: same_currency, amount: amount_a}, %Money{
889-
currency: same_currency,
890-
amount: amount_b
891-
}) do
892-
{:ok, %Money{currency: same_currency, amount: Decimal.sub(amount_a, amount_b)}}
919+
def sub(%Money{currency: same_currency, amount: amount_a},
920+
%Money{currency: same_currency, amount: amount_b} = money_b
921+
) do
922+
{:ok, %{money_b | amount: Decimal.sub(amount_a, amount_b)}}
893923
end
894924

895925
def sub(%Money{currency: code_a}, %Money{currency: code_b}) do
@@ -962,16 +992,17 @@ defmodule Money do
962992
"""
963993
@spec mult(Money.t(), Cldr.Math.number_or_decimal()) ::
964994
{:ok, Money.t()} | {:error, {module(), String.t()}}
965-
def mult(%Money{currency: code, amount: amount}, number) when is_integer(number) do
966-
{:ok, %Money{currency: code, amount: Decimal.mult(amount, Decimal.new(number))}}
995+
996+
def mult(%Money{amount: amount} = money, number) when is_integer(number) do
997+
{:ok, %{money | amount: Decimal.mult(amount, Decimal.new(number))}}
967998
end
968999

969-
def mult(%Money{currency: code, amount: amount}, number) when is_float(number) do
970-
{:ok, %Money{currency: code, amount: Decimal.mult(amount, Decimal.from_float(number))}}
1000+
def mult(%Money{amount: amount} = money, number) when is_float(number) do
1001+
{:ok, %{money | amount: Decimal.mult(amount, Decimal.from_float(number))}}
9711002
end
9721003

973-
def mult(%Money{currency: code, amount: amount}, %Decimal{} = number) do
974-
{:ok, %Money{currency: code, amount: Decimal.mult(amount, number)}}
1004+
def mult(%Money{amount: amount} = money, %Decimal{} = number) do
1005+
{:ok, %{money | amount: Decimal.mult(amount, number)}}
9751006
end
9761007

9771008
def mult(%Money{}, other) do
@@ -1040,16 +1071,17 @@ defmodule Money do
10401071
"""
10411072
@spec div(Money.t(), Cldr.Math.number_or_decimal()) ::
10421073
{:ok, Money.t()} | {:error, {module(), String.t()}}
1043-
def div(%Money{currency: code, amount: amount}, number) when is_integer(number) do
1044-
{:ok, %Money{currency: code, amount: Decimal.div(amount, Decimal.new(number))}}
1074+
1075+
def div(%Money{amount: amount} = money, number) when is_integer(number) do
1076+
{:ok, %{money | amount: Decimal.div(amount, Decimal.new(number))}}
10451077
end
10461078

1047-
def div(%Money{currency: code, amount: amount}, number) when is_float(number) do
1048-
{:ok, %Money{currency: code, amount: Decimal.div(amount, Decimal.from_float(number))}}
1079+
def div(%Money{amount: amount} = money, number) when is_float(number) do
1080+
{:ok, %{money | amount: Decimal.div(amount, Decimal.from_float(number))}}
10491081
end
10501082

1051-
def div(%Money{currency: code, amount: amount}, %Decimal{} = number) do
1052-
{:ok, %Money{currency: code, amount: Decimal.div(amount, number)}}
1083+
def div(%Money{amount: amount} = money, %Decimal{} = number) do
1084+
{:ok, %{money | amount: Decimal.div(amount, number)}}
10531085
end
10541086

10551087
def div(%Money{}, other) do
@@ -1161,6 +1193,7 @@ defmodule Money do
11611193
@spec sum([t(), ...], ExchangeRates.t()) :: {:ok, t} | {:error, {module(), String.t()}}
11621194
def sum([%Money{} = first | rest] = money_list, rates \\ %{}) when is_list(money_list) do
11631195
%Money{currency: target_currency} = first
1196+
11641197
Enum.reduce_while(rest, {:ok, first}, fn money, {:ok, acc} ->
11651198
case to_currency(money, target_currency, rates) do
11661199
{:ok, increment} -> {:cont, Money.add(acc, increment)}
@@ -1533,23 +1566,23 @@ defmodule Money do
15331566
@one Decimal.new(1)
15341567
@zero Decimal.new(0)
15351568

1536-
def put_fraction(%Money{currency: code, amount: amount}, upto) when is_integer(upto) do
1537-
with {:ok, currency} <- Currency.currency_for_code(code) do
1569+
def put_fraction(%Money{amount: amount} = money, upto) when is_integer(upto) do
1570+
with {:ok, currency} <- Currency.currency_for_code(money.currency) do
15381571
digits = currency.digits
15391572
diff = Decimal.from_float((100 - upto) * :math.pow(10, -digits))
15401573

15411574
if Cldr.Decimal.compare(diff, @one) in [:lt, :eq] &&
1542-
Cldr.Decimal.compare(@zero, diff) in [:lt, :eq] do
1575+
Cldr.Decimal.compare(@zero, diff) in [:lt, :eq] do
15431576
new_amount =
15441577
Decimal.round(amount, 0)
15451578
|> Decimal.add(@one)
15461579
|> Decimal.sub(diff)
15471580

1548-
Money.new(code, new_amount)
1581+
%{money | amount: new_amount}
15491582
else
15501583
{:error,
15511584
{Money.InvalidAmountError,
1552-
"Rounding up to #{inspect(upto)} is invalid for currency #{inspect(code)}"}}
1585+
"Rounding up to #{inspect(upto)} is invalid for currency #{inspect(money.currency)}"}}
15531586
end
15541587
end
15551588
end
@@ -1621,12 +1654,12 @@ defmodule Money do
16211654
end
16221655
end
16231656

1624-
def to_currency(%Money{currency: from_currency, amount: amount}, to_currency, %{} = rates)
1625-
when is_atom(to_currency) do
1657+
def to_currency(%Money{currency: from_currency, amount: amount} = money, to_currency, rates)
1658+
when is_atom(to_currency) and is_map(rates) do
16261659
with {:ok, to_currency_code} <- validate_currency(to_currency),
16271660
{:ok, cross_rate} <- cross_rate(from_currency, to_currency_code, rates) do
16281661
converted_amount = Decimal.mult(amount, cross_rate)
1629-
{:ok, Money.new(to_currency, converted_amount)}
1662+
{:ok, %{money | currency: to_currency, amount: converted_amount}}
16301663
end
16311664
end
16321665

@@ -1793,14 +1826,15 @@ defmodule Money do
17931826
17941827
"""
17951828
@spec normalize(Money.t()) :: Money.t()
1796-
Cldr.Macros.doc_since "5.0.0"
1829+
Cldr.Macros.doc_since("5.0.0")
1830+
17971831
if Code.ensure_loaded?(Decimal) and function_exported?(Decimal, :normalize, 1) do
1798-
def normalize(%Money{currency: currency, amount: amount}) do
1799-
%Money{currency: currency, amount: Decimal.normalize(amount)}
1832+
def normalize(%Money{amount: amount} = money) do
1833+
%{money | amount: Decimal.normalize(amount)}
18001834
end
18011835
else
1802-
def normalize(%Money{currency: currency, amount: amount}) do
1803-
%Money{currency: currency, amount: Decimal.reduce(amount)}
1836+
def normalize(%Money{amount: amount} = money) do
1837+
%{money | amount: Decimal.reduce(amount)}
18041838
end
18051839
end
18061840

@@ -1876,9 +1910,11 @@ defmodule Money do
18761910
* `currency` is the currency code for the `integer`. The assumed
18771911
decimal places is derived from the currency code.
18781912
1913+
* `options` is a keyword list of options passed to `Money.new/3`
1914+
18791915
## Returns
18801916
1881-
* A `Money` struct or
1917+
* A `t:Money` struct or
18821918
18831919
* `{:error, {Cldr.UnknownCurrencyError, message}}`
18841920
@@ -1897,21 +1933,31 @@ defmodule Money do
18971933
#Money<:COP, 200.12>
18981934
18991935
"""
1900-
@spec from_integer(integer, currency_code) :: Money.t() | {:error, module(), String.t()}
1901-
def from_integer(amount, currency) when is_integer(amount) do
1936+
@spec from_integer(integer, currency_code, Keyword.t()) ::
1937+
Money.t() | {:error, module(), String.t()}
1938+
1939+
def from_integer(amount, currency, options \\ []) when is_integer(amount) do
19021940
with {:ok, currency} <- validate_currency(currency),
19031941
{:ok, %{iso_digits: digits}} <- Currency.currency_for_code(currency) do
19041942
sign = if amount < 0, do: -1, else: 1
19051943
digits = if digits == 0, do: 0, else: -digits
19061944

19071945
sign
19081946
|> Decimal.new(Kernel.abs(amount), digits)
1909-
|> Money.new(currency)
1947+
|> Money.new(currency, options)
19101948
end
19111949
end
19121950

19131951
@doc """
1914-
Return a zero amount `Money.t` in the given currency
1952+
Return a zero amount `t:Money` in the given currency.
1953+
1954+
## Arguments
1955+
1956+
* `money_or_currency` is either a `t:Money` or
1957+
a currency code
1958+
1959+
* `options` is a keyword list of options passed
1960+
to `Money.new/3`. The default is `[]`.
19151961
19161962
## Example
19171963
@@ -1927,13 +1973,16 @@ defmodule Money do
19271973
19281974
"""
19291975
@spec zero(currency_code | Money.t()) :: Money.t()
1930-
def zero(%{currency: currency, amount: _amount}) do
1931-
zero(currency)
1976+
1977+
def zero(money_or_currency, options \\ [])
1978+
1979+
def zero(%Money{currency: currency}, options) do
1980+
zero(currency, options)
19321981
end
19331982

1934-
def zero(currency) do
1983+
def zero(currency, options) do
19351984
with {:ok, currency} <- validate_currency(currency) do
1936-
Money.new(currency, 0)
1985+
Money.new(currency, 0, options)
19371986
end
19381987
end
19391988

lib/money/application.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ defmodule Money.Application do
1010
Money.ExchangeRates.Supervisor
1111
]
1212

13-
opts = if args == [] do
14-
[strategy: :one_for_one, name: Money.Supervisor]
15-
else
16-
args
17-
end
13+
opts =
14+
if args == [] do
15+
[strategy: :one_for_one, name: Money.Supervisor]
16+
else
17+
args
18+
end
1819

1920
supervisor = Supervisor.start_link(children, opts)
2021

0 commit comments

Comments
 (0)