money 6.13.4 → 6.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +73 -2
- data/LICENSE +17 -17
- data/README.md +128 -47
- data/config/currency_backwards_compatible.json +36 -0
- data/config/currency_iso.json +144 -33
- data/config/currency_non_iso.json +17 -0
- data/lib/money/bank/variable_exchange.rb +17 -7
- data/lib/money/currency.rb +9 -4
- data/lib/money/money/allocation.rb +36 -8
- data/lib/money/money/arithmetic.rb +18 -13
- data/lib/money/money/formatter.rb +22 -12
- data/lib/money/money/formatting_rules.rb +13 -1
- data/lib/money/money.rb +129 -54
- data/lib/money/rates_store/memory.rb +24 -23
- data/lib/money/version.rb +1 -1
- data/money.gemspec +1 -3
- metadata +8 -9
data/lib/money/money.rb
CHANGED
@@ -91,51 +91,71 @@ class Money
|
|
91
91
|
class << self
|
92
92
|
|
93
93
|
# @!attribute [rw] default_bank
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
94
|
+
# Used to set a default bank for currency exchange.
|
95
|
+
#
|
96
|
+
# Each Money object is associated with a bank
|
97
|
+
# object, which is responsible for currency exchange. This property
|
98
|
+
# allows you to specify the default bank object. The default value for
|
99
|
+
# this property is an instance of +Bank::VariableExchange.+ It allows
|
100
|
+
# one to specify custom exchange rates.
|
101
|
+
#
|
102
|
+
# @return [Money::Bank::Base]
|
99
103
|
#
|
100
104
|
# @!attribute default_formatting_rules
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
+
# Used to define a default hash of rules for every time
|
106
|
+
# +Money#format+ is called. Rules provided on method call will be
|
107
|
+
# merged with the default ones. To overwrite a rule, just provide the
|
108
|
+
# intended value while calling +format+.
|
105
109
|
#
|
106
|
-
# @see
|
110
|
+
# @see Money::Formatter#initialize Money::Formatter for more details
|
107
111
|
#
|
108
112
|
# @example
|
109
113
|
# Money.default_formatting_rules = { display_free: true }
|
110
114
|
# Money.new(0, "USD").format # => "free"
|
111
115
|
# Money.new(0, "USD").format(display_free: false) # => "$0.00"
|
112
116
|
#
|
117
|
+
# @return [Hash]
|
118
|
+
#
|
113
119
|
# @!attribute [rw] use_i18n
|
114
|
-
#
|
115
|
-
#
|
120
|
+
# Used to disable i18n even if it's used by other components of your app.
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
116
123
|
#
|
117
|
-
# @!attribute [rw]
|
118
|
-
# @return [Boolean] Use this to enable infinite precision cents
|
124
|
+
# @!attribute [rw] default_infinite_precision
|
125
|
+
# @return [Boolean] Use this to enable infinite precision cents as the
|
126
|
+
# global default
|
119
127
|
#
|
120
128
|
# @!attribute [rw] conversion_precision
|
121
|
-
#
|
122
|
-
# to BigDecimal
|
123
|
-
attr_accessor :default_bank, :default_formatting_rules,
|
124
|
-
:use_i18n, :infinite_precision, :conversion_precision,
|
125
|
-
:locale_backend
|
126
|
-
|
127
|
-
# @attr_writer rounding_mode Use this to specify the rounding mode
|
129
|
+
# Used to specify precision for converting Rational to BigDecimal
|
128
130
|
#
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
# @return [Integer]
|
132
|
+
attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision
|
133
|
+
attr_reader :use_i18n, :locale_backend
|
134
|
+
attr_writer :default_bank
|
135
|
+
|
136
|
+
def infinite_precision
|
137
|
+
warn '[DEPRECATION] `Money.infinite_precision` is deprecated - use `Money.default_infinite_precision` instead'
|
138
|
+
default_infinite_precision
|
139
|
+
end
|
135
140
|
|
141
|
+
def infinite_precision=(value)
|
142
|
+
warn '[DEPRECATION] `Money.infinite_precision=` is deprecated - use `Money.default_infinite_precision= ` instead'
|
143
|
+
self.default_infinite_precision = value
|
144
|
+
end
|
136
145
|
end
|
137
146
|
|
147
|
+
# @!attribute default_currency
|
148
|
+
# @return [Money::Currency] The default currency, which is used when
|
149
|
+
# +Money.new+ is called without an explicit currency argument. The
|
150
|
+
# default value is Currency.new("USD"). The value must be a valid
|
151
|
+
# +Money::Currency+ instance.
|
138
152
|
def self.default_currency
|
153
|
+
if @using_deprecated_default_currency
|
154
|
+
warn '[WARNING] The default currency will change from `USD` to `nil` in the next major release. Make ' \
|
155
|
+
'sure to set it explicitly using `Money.default_currency=` to avoid potential issues'
|
156
|
+
@using_deprecated_default_currency = false
|
157
|
+
end
|
158
|
+
|
139
159
|
if @default_currency.respond_to?(:call)
|
140
160
|
Money::Currency.new(@default_currency.call)
|
141
161
|
else
|
@@ -143,10 +163,29 @@ class Money
|
|
143
163
|
end
|
144
164
|
end
|
145
165
|
|
166
|
+
def self.default_currency=(currency)
|
167
|
+
@using_deprecated_default_currency = false
|
168
|
+
@default_currency = currency
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.default_bank
|
172
|
+
if @default_bank.respond_to?(:call)
|
173
|
+
@default_bank.call
|
174
|
+
else
|
175
|
+
@default_bank
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
146
179
|
def self.locale_backend=(value)
|
147
180
|
@locale_backend = value ? LocaleBackend.find(value) : nil
|
148
181
|
end
|
149
182
|
|
183
|
+
# @attr_writer rounding_mode Use this to specify the rounding mode
|
184
|
+
def self.rounding_mode=(new_rounding_mode)
|
185
|
+
@using_deprecated_default_rounding_mode = false
|
186
|
+
@rounding_mode = new_rounding_mode
|
187
|
+
end
|
188
|
+
|
150
189
|
def self.use_i18n=(value)
|
151
190
|
if value
|
152
191
|
warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead for locale based formatting'
|
@@ -163,6 +202,7 @@ class Money
|
|
163
202
|
|
164
203
|
# Set the default currency for creating new +Money+ object.
|
165
204
|
self.default_currency = Currency.new("USD")
|
205
|
+
@using_deprecated_default_currency = true
|
166
206
|
|
167
207
|
# Default to using i18n
|
168
208
|
@use_i18n = true
|
@@ -171,10 +211,11 @@ class Money
|
|
171
211
|
self.locale_backend = :legacy
|
172
212
|
|
173
213
|
# Default to not using infinite precision cents
|
174
|
-
self.
|
214
|
+
self.default_infinite_precision = false
|
175
215
|
|
176
216
|
# Default to bankers rounding
|
177
217
|
self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
|
218
|
+
@using_deprecated_default_rounding_mode = true
|
178
219
|
|
179
220
|
# Default the conversion of Rationals precision to 16
|
180
221
|
self.conversion_precision = 16
|
@@ -192,18 +233,30 @@ class Money
|
|
192
233
|
#
|
193
234
|
# @return [BigDecimal::ROUND_MODE] rounding mode
|
194
235
|
def self.rounding_mode(mode = nil)
|
195
|
-
|
236
|
+
if mode
|
237
|
+
warn "[DEPRECATION] calling `rounding_mode` with a block is deprecated. Please use `.with_rounding_mode` instead."
|
238
|
+
return with_rounding_mode(mode) { yield }
|
239
|
+
end
|
240
|
+
|
241
|
+
return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]
|
196
242
|
|
197
|
-
|
198
|
-
|
243
|
+
if @using_deprecated_default_rounding_mode
|
244
|
+
warn '[WARNING] The default rounding mode will change from `ROUND_HALF_EVEN` to `ROUND_HALF_UP` in the ' \
|
245
|
+
'next major release. Set it explicitly using `Money.rounding_mode=` to avoid potential problems.'
|
246
|
+
@using_deprecated_default_rounding_mode = false
|
247
|
+
end
|
248
|
+
|
249
|
+
@rounding_mode
|
199
250
|
end
|
200
251
|
|
201
|
-
#
|
202
|
-
# results of the block instead.
|
252
|
+
# Temporarily changes the rounding mode in a given block.
|
203
253
|
#
|
204
254
|
# @param [BigDecimal::ROUND_MODE] mode
|
205
255
|
#
|
206
|
-
# @
|
256
|
+
# @yield The block within which rounding mode will be changed. Its return
|
257
|
+
# value will also be the return value of the whole method.
|
258
|
+
#
|
259
|
+
# @return [Object] block results
|
207
260
|
#
|
208
261
|
# @example
|
209
262
|
# fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_UP) do
|
@@ -241,7 +294,8 @@ class Money
|
|
241
294
|
#
|
242
295
|
# @param [Numeric] amount The numerical value of the money.
|
243
296
|
# @param [Currency, String, Symbol] currency The currency format.
|
244
|
-
# @param [
|
297
|
+
# @param [Hash] options Optional settings for the new Money instance
|
298
|
+
# @option [Money::Bank::*] :bank The exchange bank to use.
|
245
299
|
#
|
246
300
|
# @example
|
247
301
|
# Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
|
@@ -250,13 +304,17 @@ class Money
|
|
250
304
|
# @return [Money]
|
251
305
|
#
|
252
306
|
# @see #initialize
|
253
|
-
def self.from_amount(amount, currency = default_currency,
|
307
|
+
def self.from_amount(amount, currency = default_currency, options = {})
|
254
308
|
raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
|
255
309
|
|
256
310
|
currency = Currency.wrap(currency) || Money.default_currency
|
257
311
|
value = amount.to_d * currency.subunit_to_unit
|
258
|
-
value
|
259
|
-
|
312
|
+
new(value, currency, options)
|
313
|
+
end
|
314
|
+
|
315
|
+
class << self
|
316
|
+
alias_method :from_cents, :new
|
317
|
+
alias_method :from_dollars, :from_amount
|
260
318
|
end
|
261
319
|
|
262
320
|
# Creates a new Money object of value given in the
|
@@ -270,7 +328,8 @@ class Money
|
|
270
328
|
# argument, a Money will be created in that currency with fractional value
|
271
329
|
# = 0.
|
272
330
|
# @param [Currency, String, Symbol] currency The currency format.
|
273
|
-
# @param [
|
331
|
+
# @param [Hash] options Optional settings for the new Money instance
|
332
|
+
# @option [Money::Bank::*] :bank The exchange bank to use.
|
274
333
|
#
|
275
334
|
# @return [Money]
|
276
335
|
#
|
@@ -279,11 +338,17 @@ class Money
|
|
279
338
|
# Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
|
280
339
|
# Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
|
281
340
|
#
|
282
|
-
def initialize(obj, currency = Money.default_currency,
|
341
|
+
def initialize( obj, currency = Money.default_currency, options = {})
|
342
|
+
# For backwards compatability, if options is not a Hash, treat it as a bank parameter
|
343
|
+
unless options.is_a?(Hash)
|
344
|
+
options = { bank: options }
|
345
|
+
end
|
346
|
+
|
283
347
|
@fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
|
284
348
|
@currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
|
285
349
|
@currency ||= Money.default_currency
|
286
|
-
@bank = obj.respond_to?(:bank) ? obj.bank : bank
|
350
|
+
@bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
|
351
|
+
@bank ||= Money.default_bank
|
287
352
|
|
288
353
|
# BigDecimal can be Infinity and NaN, money of that amount does not make sense
|
289
354
|
raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
|
@@ -432,7 +497,7 @@ class Money
|
|
432
497
|
if !new_currency || currency == new_currency
|
433
498
|
self
|
434
499
|
else
|
435
|
-
|
500
|
+
dup_with(currency: new_currency)
|
436
501
|
end
|
437
502
|
end
|
438
503
|
|
@@ -509,13 +574,15 @@ class Money
|
|
509
574
|
exchange_to("EUR")
|
510
575
|
end
|
511
576
|
|
512
|
-
# Splits a given amount in parts without
|
513
|
-
# distributed round-robin amongst the parties. This means that
|
514
|
-
# receive more pennies than ones
|
577
|
+
# Splits a given amount in parts without losing pennies. The left-over pennies will be
|
578
|
+
# distributed round-robin amongst the parties. This means that parts listed first will likely
|
579
|
+
# receive more pennies than ones listed later.
|
515
580
|
#
|
516
|
-
#
|
517
|
-
#
|
518
|
-
# number instead of an array will split the amount evenly (without
|
581
|
+
# Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
|
582
|
+
# part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
|
583
|
+
# number instead of an array will split the amount evenly (without losing pennies when rounding).
|
584
|
+
#
|
585
|
+
# @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
|
519
586
|
#
|
520
587
|
# @return [Array<Money>]
|
521
588
|
#
|
@@ -526,8 +593,8 @@ class Money
|
|
526
593
|
# Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
527
594
|
#
|
528
595
|
def allocate(parts)
|
529
|
-
amounts = Money::Allocation.generate(fractional, parts, !Money.
|
530
|
-
amounts.map { |amount|
|
596
|
+
amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
|
597
|
+
amounts.map { |amount| dup_with(fractional: amount) }
|
531
598
|
end
|
532
599
|
alias_method :split, :allocate
|
533
600
|
|
@@ -544,16 +611,16 @@ class Money
|
|
544
611
|
# Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
|
545
612
|
#
|
546
613
|
# @see
|
547
|
-
# Money.
|
614
|
+
# Money.default_infinite_precision
|
548
615
|
#
|
549
616
|
def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
|
550
617
|
rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
|
551
|
-
|
618
|
+
dup_with(fractional: rounded_amount)
|
552
619
|
end
|
553
620
|
|
554
621
|
# Creates a formatted price string according to several rules.
|
555
622
|
#
|
556
|
-
# @param [Hash] See Money::Formatter for the list of formatting options
|
623
|
+
# @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
|
557
624
|
#
|
558
625
|
# @return [String]
|
559
626
|
#
|
@@ -579,6 +646,14 @@ class Money
|
|
579
646
|
Money::Formatter::DEFAULTS[:decimal_mark]
|
580
647
|
end
|
581
648
|
|
649
|
+
def dup_with(options = {})
|
650
|
+
self.class.new(
|
651
|
+
options[:fractional] || fractional,
|
652
|
+
options[:currency] || currency,
|
653
|
+
bank: options[:bank] || bank
|
654
|
+
)
|
655
|
+
end
|
656
|
+
|
582
657
|
private
|
583
658
|
|
584
659
|
def as_d(num)
|
@@ -590,7 +665,7 @@ class Money
|
|
590
665
|
end
|
591
666
|
|
592
667
|
def return_value(value)
|
593
|
-
if self.class.
|
668
|
+
if self.class.default_infinite_precision
|
594
669
|
value
|
595
670
|
else
|
596
671
|
value.round(0, self.class.rounding_mode).to_i
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
1
3
|
class Money
|
2
4
|
module RatesStore
|
3
5
|
|
@@ -17,11 +19,11 @@ class Money
|
|
17
19
|
#
|
18
20
|
# @param [Hash] opts Optional store options.
|
19
21
|
# @option opts [Boolean] :without_mutex disables the usage of a mutex
|
20
|
-
# @param [Hash]
|
21
|
-
def initialize(opts = {},
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@
|
22
|
+
# @param [Hash] rates Optional initial exchange rate data.
|
23
|
+
def initialize(opts = {}, rates = {})
|
24
|
+
@rates = rates
|
25
|
+
@options = opts
|
26
|
+
@guard = Monitor.new
|
25
27
|
end
|
26
28
|
|
27
29
|
# Registers a conversion rate and returns it. Uses +Mutex+ to synchronize data access.
|
@@ -37,7 +39,9 @@ class Money
|
|
37
39
|
# store.add_rate("USD", "CAD", 1.24515)
|
38
40
|
# store.add_rate("CAD", "USD", 0.803115)
|
39
41
|
def add_rate(currency_iso_from, currency_iso_to, rate)
|
40
|
-
|
42
|
+
guard.synchronize do
|
43
|
+
rates[rate_key_for(currency_iso_from, currency_iso_to)] = rate
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
47
|
# Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize data access.
|
@@ -54,24 +58,21 @@ class Money
|
|
54
58
|
#
|
55
59
|
# store.get_rate("USD", "CAD") #=> 1.24515
|
56
60
|
def get_rate(currency_iso_from, currency_iso_to)
|
57
|
-
|
61
|
+
guard.synchronize do
|
62
|
+
rates[rate_key_for(currency_iso_from, currency_iso_to)]
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
60
66
|
def marshal_dump
|
61
|
-
|
67
|
+
guard.synchronize do
|
68
|
+
return [self.class, options, rates.dup]
|
69
|
+
end
|
62
70
|
end
|
63
71
|
|
64
72
|
# Wraps block execution in a thread-safe transaction
|
65
73
|
def transaction(&block)
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
@mutex.synchronize do
|
70
|
-
@in_transaction = true
|
71
|
-
result = block.call
|
72
|
-
@in_transaction = false
|
73
|
-
result
|
74
|
-
end
|
74
|
+
guard.synchronize do
|
75
|
+
yield
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
@@ -88,19 +89,19 @@ class Money
|
|
88
89
|
# puts [iso_from, iso_to, rate].join
|
89
90
|
# end
|
90
91
|
def each_rate(&block)
|
91
|
-
|
92
|
-
|
92
|
+
return to_enum(:each_rate) unless block_given?
|
93
|
+
|
94
|
+
guard.synchronize do
|
95
|
+
rates.each do |key, rate|
|
93
96
|
iso_from, iso_to = key.split(INDEX_KEY_SEPARATOR)
|
94
|
-
|
97
|
+
yield iso_from, iso_to, rate
|
95
98
|
end
|
96
99
|
end
|
97
|
-
|
98
|
-
block_given? ? enum.each(&block) : enum
|
99
100
|
end
|
100
101
|
|
101
102
|
private
|
102
103
|
|
103
|
-
attr_reader :
|
104
|
+
attr_reader :rates, :options, :guard
|
104
105
|
|
105
106
|
# Return the rate hashkey for the given currencies.
|
106
107
|
#
|
data/lib/money/version.rb
CHANGED
data/money.gemspec
CHANGED
@@ -20,11 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "rspec", "~> 3.4"
|
22
22
|
s.add_development_dependency "yard", "~> 0.9.11"
|
23
|
-
s.add_development_dependency "kramdown", "~>
|
23
|
+
s.add_development_dependency "kramdown", "~> 2.3"
|
24
24
|
|
25
25
|
s.files = `git ls-files -z -- config/* lib/* CHANGELOG.md LICENSE money.gemspec README.md`.split("\x0")
|
26
|
-
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
27
|
-
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
28
26
|
s.require_paths = ["lib"]
|
29
27
|
|
30
28
|
if s.respond_to?(:metadata)
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Emmons
|
8
8
|
- Anthony Dmitriyev
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-03-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: i18n
|
@@ -93,14 +93,14 @@ dependencies:
|
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '2.3'
|
97
97
|
type: :development
|
98
98
|
prerelease: false
|
99
99
|
version_requirements: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '2.3'
|
104
104
|
description: A Ruby Library for dealing with money and currency conversion.
|
105
105
|
email:
|
106
106
|
- shane@emmons.io
|
@@ -144,7 +144,7 @@ metadata:
|
|
144
144
|
changelog_uri: https://github.com/RubyMoney/money/blob/master/CHANGELOG.md
|
145
145
|
source_code_uri: https://github.com/RubyMoney/money/
|
146
146
|
bug_tracker_uri: https://github.com/RubyMoney/money/issues
|
147
|
-
post_install_message:
|
147
|
+
post_install_message:
|
148
148
|
rdoc_options: []
|
149
149
|
require_paths:
|
150
150
|
- lib
|
@@ -159,9 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
159
|
- !ruby/object:Gem::Version
|
160
160
|
version: '0'
|
161
161
|
requirements: []
|
162
|
-
|
163
|
-
|
164
|
-
signing_key:
|
162
|
+
rubygems_version: 3.5.3
|
163
|
+
signing_key:
|
165
164
|
specification_version: 4
|
166
165
|
summary: A Ruby Library for dealing with money and currency conversion.
|
167
166
|
test_files: []
|