Skip to content

Commit 8ad76df

Browse files
committed
Do not hardcode helper methods for specific currencies
1 parent f4f7ceb commit 8ad76df

File tree

9 files changed

+167
-276
lines changed

9 files changed

+167
-276
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,23 @@ Money.locale_backend = :i18n
6363

6464
# 10.00 USD
6565
money = Money.from_cents(1000, "USD")
66-
money.cents #=> 1000
67-
money.currency #=> Currency.new("USD")
66+
money.cents #=> 1000
67+
money.currency #=> Currency.new("USD")
68+
69+
# Constructors
70+
Money.new(1000, "USD") #=> #<Money fractional:1000 currency:USD>
71+
Money.from_cents(1000, "USD") #=> #<Money fractional:1000 currency:USD>
72+
Money.from_amount(10, "USD") #=> #<Money fractional:1000 currency:USD>
73+
Money.usd(1000) #=> #<Money fractional:1000 currency:USD>
74+
Money.us_dollar(500) #=> #<Money fractional:500 currency:USD>
6875

6976
# Comparisons
7077
Money.from_cents(1000, "USD") == Money.from_cents(1000, "USD") #=> true
7178
Money.from_cents(1000, "USD") == Money.from_cents(100, "USD") #=> false
7279
Money.from_cents(1000, "USD") == Money.from_cents(1000, "EUR") #=> false
7380
Money.from_cents(1000, "USD") != Money.from_cents(1000, "EUR") #=> true
81+
Money.from_cents(0, "USD") == Money.from_cents(0, "EUR") #=> true
82+
Money.from_cents(0, "USD") == 0 #=> true
7483

7584
# Arithmetic
7685
Money.from_cents(1000, "USD") + Money.from_cents(500, "USD") == Money.from_cents(1500, "USD")
@@ -85,7 +94,9 @@ Money.from_amount(5, "TND") == Money.from_cents(5000, "TND") # 5 TND
8594

8695
# Currency conversions
8796
some_code_to_setup_exchange_rates
88-
Money.from_cents(1000, "USD").exchange_to("EUR") == Money.from_cents(some_value, "EUR")
97+
Money.from_cents(1000, "EUR").exchange_to("USD") == Money.from_cents(some_value, "USD")
98+
Money.from_cents(1000, "EUR").as_usd == Money.from_cents(some_value, "USD")
99+
Money.from_cents(1000, "EUR").as_us_dollar == Money.from_cents(some_value, "USD")
89100

90101
# Swap currency
91102
Money.from_cents(1000, "USD").with_currency("EUR") == Money.from_cents(1000, "EUR")
@@ -96,6 +107,27 @@ Money.from_cents(100, "GBP").format #=> "£1.00"
96107
Money.from_cents(100, "EUR").format #=> "€1.00"
97108
```
98109

110+
### Helpers
111+
112+
You can define shorthand methods for creating `Money` objects and exchanging between frequently used currencies:
113+
114+
```ruby
115+
Money.currency_helpers = {
116+
usd: 'USD',
117+
us_dollar: 'USD',
118+
mxn: 'MXN',
119+
}
120+
121+
# Constructors
122+
Money.mxn(200) #=> #<Money fractional:200 currency:MXN>
123+
124+
# Currency conversions
125+
Money.add_rate("USD", "MXN", 150)
126+
Money.new(100, "USD").as_mxn #=> #<Money fractional:15000 currency:MXN>
127+
```
128+
129+
Some common helpers are defined by default: `usd`, `us_dollar`, `cad`, `ca_dollar`, `eur`, `euro`, `gbp`, `pound_sterling`. These defaults will be removed in a future version.
130+
99131
## Currency
100132

101133
Currencies are consistently represented as instances of `Money::Currency`.
@@ -231,8 +263,8 @@ an example of how it works:
231263
Money.add_rate("USD", "CAD", 1.24515)
232264
Money.add_rate("CAD", "USD", 0.803115)
233265

234-
Money.us_dollar(100).exchange_to("CAD") # => Money.from_cents(124, "CAD")
235-
Money.ca_dollar(100).exchange_to("USD") # => Money.from_cents(80, "USD")
266+
Money.new(100, "USD").exchange_to("CAD") # => Money.from_cents(124, "CAD")
267+
Money.new(100, "CAD").exchange_to("USD") # => Money.from_cents(80, "USD")
236268
```
237269

238270
Comparison and arithmetic operations work as expected:
@@ -601,7 +633,7 @@ Money.from_cents(10_000_00, 'EUR').format # => €10000.00
601633
In case you're working with collections of `Money` instances, have a look at [money-collection](https://github.com/RubyMoney/money-collection)
602634
for improved performance and accuracy.
603635

604-
### Troubleshooting
636+
## Troubleshooting
605637

606638
If you don't have some locale and don't want to get a runtime error such as:
607639

lib/money/money.rb

Lines changed: 35 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ class << self
161161
:default_infinite_precision,
162162
:conversion_precision,
163163
:strict_eql_compare
164-
attr_reader :locale_backend
164+
attr_reader :locale_backend,
165+
:currency_helpers
165166
attr_writer :default_bank,
166167
:default_currency
167168

@@ -219,6 +220,21 @@ def self.locale_backend=(value)
219220
@locale_backend = value ? LocaleBackend.find(value) : nil
220221
end
221222

223+
def self.currency_helpers=(hash)
224+
hash.each do |name, currency|
225+
next if singleton_class.method_defined?(name)
226+
227+
singleton_class.define_method(name) do |cents|
228+
new(cents, currency)
229+
end
230+
231+
define_method("as_#{name}") do
232+
exchange_to(currency)
233+
end
234+
end
235+
@currency_helpers = hash
236+
end
237+
222238
def self.setup_defaults
223239
# Set the default bank for creating new +Money+ objects.
224240
self.default_bank = Bank::VariableExchange.instance
@@ -235,9 +251,21 @@ def self.setup_defaults
235251
# Default the conversion of Rationals precision to 16
236252
self.conversion_precision = 16
237253

238-
# Defaults to the deprecated behavior where
254+
# Default to the deprecated behavior where
239255
# `Money.new(0, "USD").eql?(Money.new(0, "EUR"))` is true.
240256
self.strict_eql_compare = false
257+
258+
# Default to currencies previously hardcoded as methods
259+
self.currency_helpers = {
260+
cad: "CAD",
261+
ca_dollar: "CAD",
262+
eur: "EUR",
263+
euro: "EUR",
264+
gbp: "GBP",
265+
pound_sterling: "GBP",
266+
usd: "USD",
267+
us_dollar: "USD",
268+
}
241269
end
242270

243271
def self.inherited(base)
@@ -298,41 +326,6 @@ def self.disallow_currency_conversion!
298326
self.default_bank = Bank::SingleCurrency.instance
299327
end
300328

301-
# Creates a new Money object of value given in the +unit+ of the given
302-
# +currency+.
303-
#
304-
# @param [Numeric] amount The numerical value of the money.
305-
# @param [Currency, String, Symbol] currency The currency format.
306-
# @param [Hash] options Optional settings for the new Money instance
307-
# @option [Money::Bank::*] :bank The exchange bank to use.
308-
#
309-
# @example
310-
# Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
311-
# Money.from_amount(23.45, "JPY") # => #<Money fractional:23 currency:JPY>
312-
#
313-
# @return [Money]
314-
#
315-
# @see #initialize
316-
def self.from_amount(amount, currency = default_currency, options = {})
317-
raise ArgumentError, "'amount' must be numeric" unless amount.is_a?(Numeric)
318-
319-
currency = Currency.wrap(currency) || Money.default_currency
320-
raise Currency::NoCurrency, "must provide a currency" if currency.nil?
321-
322-
value = amount.to_d * currency.subunit_to_unit
323-
new(value, currency, options)
324-
end
325-
326-
# DEPRECATED.
327-
#
328-
# @see Money.from_amount
329-
def self.from_dollars(amount, currency = default_currency, options = {})
330-
warn "[DEPRECATION] `Money.from_dollars` is deprecated in favor of " \
331-
"`Money.from_amount`."
332-
333-
from_amount(amount, currency, options)
334-
end
335-
336329
# Creates a new Money object of value given in the
337330
# +fractional unit+ of the given +currency+.
338331
#
@@ -427,7 +420,7 @@ def inspect
427420
# @return [String]
428421
#
429422
# @example
430-
# Money.ca_dollar(100).to_s #=> "1.00"
423+
# Money.new(100, "CAD").to_s #=> "1.00"
431424
def to_s
432425
format thousands_separator: "",
433426
no_cents_if_whole: currency.decimal_places == 0,
@@ -440,7 +433,7 @@ def to_s
440433
# @return [BigDecimal]
441434
#
442435
# @example
443-
# Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")
436+
# Money.new(1_00, "USD").to_d #=> BigDecimal("1.00")
444437
def to_d
445438
as_d(fractional) / as_d(currency.subunit_to_unit)
446439
end
@@ -450,20 +443,20 @@ def to_d
450443
# @return [Integer]
451444
#
452445
# @example
453-
# Money.us_dollar(1_00).to_i #=> 1
446+
# Money.new(1_00, "USD").to_i #=> 1
454447
def to_i
455448
to_d.to_i
456449
end
457450

458-
# Return the amount of money as a float. Floating points cannot guarantee
451+
# Returns the amount of money as a float. Floating points cannot guarantee
459452
# precision. Therefore, this function should only be used when you no longer
460453
# need to represent currency or working with another system that requires
461454
# floats.
462455
#
463456
# @return [Float]
464457
#
465458
# @example
466-
# Money.us_dollar(100).to_f #=> 1.0
459+
# Money.new(100, "USD").to_f #=> 1.0
467460
def to_f
468461
to_d.to_f
469462
end
@@ -520,42 +513,6 @@ def exchange_to(other_currency, &)
520513
end
521514
end
522515

523-
# Receive a money object with the same amount as the current Money object
524-
# in United States dollar.
525-
#
526-
# @return [Money]
527-
#
528-
# @example
529-
# n = Money.new(100, "CAD").as_us_dollar
530-
# n.currency #=> #<Money::Currency id: usd>
531-
def as_us_dollar
532-
exchange_to("USD")
533-
end
534-
535-
# Receive a money object with the same amount as the current Money object
536-
# in Canadian dollar.
537-
#
538-
# @return [Money]
539-
#
540-
# @example
541-
# n = Money.new(100, "USD").as_ca_dollar
542-
# n.currency #=> #<Money::Currency id: cad>
543-
def as_ca_dollar
544-
exchange_to("CAD")
545-
end
546-
547-
# Receive a money object with the same amount as the current Money object
548-
# in euro.
549-
#
550-
# @return [Money]
551-
#
552-
# @example
553-
# n = Money.new(100, "USD").as_euro
554-
# n.currency #=> #<Money::Currency id: eur>
555-
def as_euro
556-
exchange_to("EUR")
557-
end
558-
559516
# Splits a given amount in parts without losing pennies. The left-over pennies will be
560517
# distributed round-robin amongst the parties. This means that parts listed first will likely
561518
# receive more pennies than ones listed later.

lib/money/money/constructors.rb

Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class Money
44
module Constructors
5-
# Create a new money object with value 0.
5+
# Creates a new Money object with value 0.
66
#
77
# @param [Currency, String, Symbol] currency The currency to use.
88
#
@@ -16,70 +16,39 @@ def empty(currency = default_currency)
1616

1717
alias zero empty
1818

19-
# Creates a new Money object of the given value, using the Canadian
20-
# dollar currency.
19+
# Creates a new Money object of value given in the +unit+ of the given
20+
# +currency+.
2121
#
22-
# @param [Integer] cents The cents value.
23-
#
24-
# @return [Money]
22+
# @param [Numeric] amount The numerical value of the money.
23+
# @param [Currency, String, Symbol] currency The currency format.
24+
# @param [Hash] options Optional settings for the new Money instance
25+
# @option [Money::Bank::*] :bank The exchange bank to use.
2526
#
2627
# @example
27-
# n = Money.ca_dollar(100)
28-
# n.cents #=> 100
29-
# n.currency #=> #<Money::Currency id: cad>
30-
def ca_dollar(cents)
31-
new(cents, "CAD")
32-
end
33-
34-
alias cad ca_dollar
35-
36-
# Creates a new Money object of the given value, using the American dollar
37-
# currency.
38-
#
39-
# @param [Integer] cents The cents value.
28+
# Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
29+
# Money.from_amount(23.45, "JPY") # => #<Money fractional:23 currency:JPY>
4030
#
4131
# @return [Money]
4232
#
43-
# @example
44-
# n = Money.us_dollar(100)
45-
# n.cents #=> 100
46-
# n.currency #=> #<Money::Currency id: usd>
47-
def us_dollar(cents)
48-
new(cents, "USD")
49-
end
33+
# @see #initialize
34+
def from_amount(amount, currency = default_currency, options = {})
35+
raise ArgumentError, "'amount' must be numeric" unless amount.is_a?(Numeric)
5036

51-
alias usd us_dollar
37+
currency = Currency.wrap(currency) || Money.default_currency
38+
raise Currency::NoCurrency, "must provide a currency" if currency.nil?
5239

53-
# Creates a new Money object of the given value, using the Euro currency.
54-
#
55-
# @param [Integer] cents The cents value.
56-
#
57-
# @return [Money]
58-
#
59-
# @example
60-
# n = Money.euro(100)
61-
# n.cents #=> 100
62-
# n.currency #=> #<Money::Currency id: eur>
63-
def euro(cents)
64-
new(cents, "EUR")
40+
value = amount.to_d * currency.subunit_to_unit
41+
new(value, currency, options)
6542
end
6643

67-
alias eur euro
68-
69-
# Creates a new Money object of the given value, in British pounds.
70-
#
71-
# @param [Integer] pence The pence value.
44+
# DEPRECATED.
7245
#
73-
# @return [Money]
74-
#
75-
# @example
76-
# n = Money.pound_sterling(100)
77-
# n.fractional #=> 100
78-
# n.currency #=> #<Money::Currency id: gbp>
79-
def pound_sterling(pence)
80-
new(pence, "GBP")
81-
end
46+
# @see Money.from_amount
47+
def from_dollars(amount, currency = default_currency, options = {})
48+
warn "[DEPRECATION] `Money.from_dollars` is deprecated in favor of " \
49+
"`Money.from_amount`."
8250

83-
alias gbp pound_sterling
51+
from_amount(amount, currency, options)
52+
end
8453
end
8554
end

0 commit comments

Comments
 (0)