money 6.11.3 → 6.12.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +3 -0
- data/lib/money/money.rb +37 -75
- data/lib/money/money/allocation.rb +37 -0
- data/lib/money/money/constructors.rb +1 -3
- data/lib/money/money/formatter.rb +41 -6
- data/lib/money/version.rb +1 -1
- data/spec/money/allocation_spec.rb +130 -0
- data/spec/money/constructors_spec.rb +0 -20
- data/spec/money/formatting_spec.rb +23 -0
- data/spec/money_spec.rb +59 -24
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 833d36d87efd793fa9e73986df700da145b59578
|
|
4
|
+
data.tar.gz: 5fae93b474a739c79e45c18e45b3d652c4f1d4d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77560afc19c7447c654daa551924ee3a0cd84e17a880d64b9d012aefda8fabc23ea100ad229278937817025f986839d9c6112c04cd8b1ee862ca0faf02fe9b4c
|
|
7
|
+
data.tar.gz: 8c126aadb1cd5beedff854a8353a46a423296ceccdc8e1f72b7c65016937df8cb643c78c735fa87327bb41838ff12a76919a561e35ad5d3cb3eeca2573b5f526
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.12.0
|
|
4
|
+
- Remove caching of `.empty`/`.zero`
|
|
5
|
+
- Preserve assigned bank when rounding
|
|
6
|
+
- Always round the fractional part when calling `#round`
|
|
7
|
+
- Wrap all amount parts when `:html_wrap` option is used
|
|
8
|
+
- Deprecate `#currency_as_string` and `#currency_as_string=` (in favour of `#with_currency`)
|
|
9
|
+
- Add `#with_currency` for swapping the currency
|
|
10
|
+
- Rewrite allocate/split (fixing some penny loosing issues)
|
|
11
|
+
|
|
3
12
|
## 6.11.3
|
|
4
13
|
- Fix regression: if enabled use i18n locales in Money#to_s
|
|
5
14
|
|
data/README.md
CHANGED
|
@@ -88,6 +88,9 @@ Money.from_amount(5, "TND") == Money.new(5000, "TND") # 5 TND
|
|
|
88
88
|
some_code_to_setup_exchange_rates
|
|
89
89
|
Money.new(1000, "USD").exchange_to("EUR") == Money.new(some_value, "EUR")
|
|
90
90
|
|
|
91
|
+
# Swap currency
|
|
92
|
+
Money.new(1000, "USD").with_currency("EUR") == Money.new(1000, "EUR")
|
|
93
|
+
|
|
91
94
|
# Formatting (see Formatting section for more options)
|
|
92
95
|
Money.new(100, "USD").format #=> "$1.00"
|
|
93
96
|
Money.new(100, "GBP").format #=> "£1.00"
|
data/lib/money/money.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "money/bank/single_currency"
|
|
|
4
4
|
require "money/money/arithmetic"
|
|
5
5
|
require "money/money/constructors"
|
|
6
6
|
require "money/money/formatter"
|
|
7
|
+
require "money/money/allocation"
|
|
7
8
|
|
|
8
9
|
# "Money is any object or record that is generally accepted as payment for
|
|
9
10
|
# goods and services and repayment of debts in a given socio-economic context
|
|
@@ -227,7 +228,8 @@ class Money
|
|
|
227
228
|
#
|
|
228
229
|
# @see #initialize
|
|
229
230
|
def self.from_amount(amount, currency = default_currency, bank = default_bank)
|
|
230
|
-
|
|
231
|
+
raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
|
|
232
|
+
|
|
231
233
|
currency = Currency.wrap(currency) || Money.default_currency
|
|
232
234
|
value = amount.to_d * currency.subunit_to_unit
|
|
233
235
|
value = value.round(0, rounding_mode) unless infinite_precision
|
|
@@ -301,6 +303,7 @@ class Money
|
|
|
301
303
|
# @example
|
|
302
304
|
# Money.new(100, :USD).currency_as_string #=> "USD"
|
|
303
305
|
def currency_as_string
|
|
306
|
+
warn "[DEPRECATION] `currency_as_string` is deprecated. Please use `.currency.to_s` instead."
|
|
304
307
|
currency.to_s
|
|
305
308
|
end
|
|
306
309
|
|
|
@@ -313,6 +316,8 @@ class Money
|
|
|
313
316
|
# @example
|
|
314
317
|
# Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
|
|
315
318
|
def currency_as_string=(val)
|
|
319
|
+
warn "[DEPRECATION] `currency_as_string=` is deprecated - Money instances are immutable." \
|
|
320
|
+
" Please use `with_currency` instead."
|
|
316
321
|
@currency = Currency.wrap(val)
|
|
317
322
|
end
|
|
318
323
|
|
|
@@ -390,7 +395,22 @@ class Money
|
|
|
390
395
|
to_d.to_f
|
|
391
396
|
end
|
|
392
397
|
|
|
393
|
-
#
|
|
398
|
+
# Returns a new Money instance in a given currency leaving the amount intact
|
|
399
|
+
# and not performing currency conversion.
|
|
400
|
+
#
|
|
401
|
+
# @param [Currency, String, Symbol] new_currency Currency of the new object.
|
|
402
|
+
#
|
|
403
|
+
# @return [self]
|
|
404
|
+
def with_currency(new_currency)
|
|
405
|
+
new_currency = Currency.wrap(new_currency)
|
|
406
|
+
if !new_currency || currency == new_currency
|
|
407
|
+
self
|
|
408
|
+
else
|
|
409
|
+
self.class.new(fractional, new_currency, bank)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Conversion to +self+.
|
|
394
414
|
#
|
|
395
415
|
# @return [self]
|
|
396
416
|
def to_money(given_currency = nil)
|
|
@@ -463,49 +483,27 @@ class Money
|
|
|
463
483
|
exchange_to("EUR")
|
|
464
484
|
end
|
|
465
485
|
|
|
466
|
-
#
|
|
467
|
-
#
|
|
468
|
-
#
|
|
469
|
-
# listed first will likely receive more pennies than ones that are listed later
|
|
486
|
+
# Splits a given amount in parts without loosing pennies. The left-over pennies will be
|
|
487
|
+
# distributed round-robin amongst the parties. This means that parties listed first will likely
|
|
488
|
+
# receive more pennies than ones that are listed later.
|
|
470
489
|
#
|
|
471
|
-
# @param [Array<Numeric
|
|
472
|
-
#
|
|
490
|
+
# @param [Array<Numeric>, Numeric] pass [2, 1, 1] to give twice as much to party1 as party2 or
|
|
491
|
+
# party3 which results in 50% of the cash to party1, 25% to party2, and 25% to party3. Passing a
|
|
492
|
+
# number instead of an array will split the amount evenly (without loosing pennies when rounding).
|
|
473
493
|
#
|
|
474
494
|
# @return [Array<Money>]
|
|
475
495
|
#
|
|
476
496
|
# @example
|
|
477
|
-
# Money.new(5, "USD").allocate([
|
|
497
|
+
# Money.new(5, "USD").allocate([3, 7]) #=> [Money.new(2), Money.new(3)]
|
|
478
498
|
# Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
|
499
|
+
# Money.new(100, "USD").allocate(2) #=> [Money.new(50), Money.new(50)]
|
|
500
|
+
# Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
|
479
501
|
#
|
|
480
|
-
def allocate(
|
|
481
|
-
amounts
|
|
482
|
-
|
|
483
|
-
unless self.class.infinite_precision
|
|
484
|
-
delta = left_over > 0 ? 1 : -1
|
|
485
|
-
# Distribute left over pennies amongst allocations
|
|
486
|
-
left_over.to_i.abs.times { |i| amounts[i % amounts.length] += delta }
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
amounts.collect { |fractional| self.class.new(fractional, currency) }
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Split money amongst parties evenly without losing pennies.
|
|
493
|
-
#
|
|
494
|
-
# @param [Numeric] num number of parties.
|
|
495
|
-
#
|
|
496
|
-
# @return [Array<Money>]
|
|
497
|
-
#
|
|
498
|
-
# @example
|
|
499
|
-
# Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
|
500
|
-
def split(num)
|
|
501
|
-
raise ArgumentError, "need at least one party" if num < 1
|
|
502
|
-
|
|
503
|
-
if self.class.infinite_precision
|
|
504
|
-
split_infinite(num)
|
|
505
|
-
else
|
|
506
|
-
split_flat(num)
|
|
507
|
-
end
|
|
502
|
+
def allocate(parts)
|
|
503
|
+
amounts = Money::Allocation.generate(fractional, parts, !Money.infinite_precision)
|
|
504
|
+
amounts.map { |amount| self.class.new(amount, currency) }
|
|
508
505
|
end
|
|
506
|
+
alias_method :split, :allocate
|
|
509
507
|
|
|
510
508
|
# Round the monetary amount to smallest unit of coinage.
|
|
511
509
|
#
|
|
@@ -523,11 +521,8 @@ class Money
|
|
|
523
521
|
# Money.infinite_precision
|
|
524
522
|
#
|
|
525
523
|
def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
else
|
|
529
|
-
self
|
|
530
|
-
end
|
|
524
|
+
rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
|
|
525
|
+
self.class.new(rounded_amount, currency, bank)
|
|
531
526
|
end
|
|
532
527
|
|
|
533
528
|
# Creates a formatted price string according to several rules.
|
|
@@ -566,39 +561,6 @@ class Money
|
|
|
566
561
|
end
|
|
567
562
|
end
|
|
568
563
|
|
|
569
|
-
def amounts_from_splits(splits)
|
|
570
|
-
allocations = splits.inject(0, :+)
|
|
571
|
-
left_over = fractional
|
|
572
|
-
|
|
573
|
-
amounts = splits.map do |ratio|
|
|
574
|
-
if self.class.infinite_precision
|
|
575
|
-
fractional * ratio
|
|
576
|
-
else
|
|
577
|
-
(fractional * ratio / allocations).truncate.tap do |frac|
|
|
578
|
-
left_over -= frac
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
end
|
|
582
|
-
|
|
583
|
-
[amounts, left_over]
|
|
584
|
-
end
|
|
585
|
-
|
|
586
|
-
def split_infinite(num)
|
|
587
|
-
amt = div(as_d(num))
|
|
588
|
-
1.upto(num).map{amt}
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
def split_flat(num)
|
|
592
|
-
low = self.class.new(fractional / num, currency)
|
|
593
|
-
high = self.class.new(low.fractional + 1, currency)
|
|
594
|
-
|
|
595
|
-
remainder = fractional % num
|
|
596
|
-
|
|
597
|
-
Array.new(num).each_with_index.map do |_, index|
|
|
598
|
-
index < remainder ? high : low
|
|
599
|
-
end
|
|
600
|
-
end
|
|
601
|
-
|
|
602
564
|
def return_value(value)
|
|
603
565
|
if self.class.infinite_precision
|
|
604
566
|
value
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
class Money
|
|
4
|
+
class Allocation
|
|
5
|
+
# Splits a given amount in parts without loosing pennies.
|
|
6
|
+
# The left-over pennies will be distributed round-robin amongst the parties. This means that
|
|
7
|
+
# parties listed first will likely receive more pennies than ones that are listed later.
|
|
8
|
+
#
|
|
9
|
+
# The results should always add up to the original amount.
|
|
10
|
+
#
|
|
11
|
+
# The parts can be specified as:
|
|
12
|
+
# Numeric — performs the split between a given number of parties evenely
|
|
13
|
+
# Array<Numeric> — allocates the amounts proportionally to the given array
|
|
14
|
+
#
|
|
15
|
+
def self.generate(amount, parts, whole_amounts = true)
|
|
16
|
+
parts = parts.is_a?(Numeric) ? Array.new(parts, 1) : parts.dup
|
|
17
|
+
|
|
18
|
+
raise ArgumentError, 'need at least one party' if parts.empty?
|
|
19
|
+
|
|
20
|
+
result = []
|
|
21
|
+
remaining_amount = amount
|
|
22
|
+
|
|
23
|
+
until parts.empty? do
|
|
24
|
+
parts_sum = parts.inject(0, :+)
|
|
25
|
+
part = parts.pop
|
|
26
|
+
|
|
27
|
+
current_split = remaining_amount * part / parts_sum
|
|
28
|
+
current_split = current_split.truncate if whole_amounts
|
|
29
|
+
|
|
30
|
+
result.unshift current_split
|
|
31
|
+
remaining_amount -= current_split
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -10,9 +10,7 @@ class Money
|
|
|
10
10
|
# @example
|
|
11
11
|
# Money.empty #=> #<Money @fractional=0>
|
|
12
12
|
def empty(currency = default_currency)
|
|
13
|
-
|
|
14
|
-
currency = Currency.new(currency)
|
|
15
|
-
@empty[currency] ||= new(0, currency).freeze
|
|
13
|
+
new(0, currency)
|
|
16
14
|
end
|
|
17
15
|
alias_method :zero, :empty
|
|
18
16
|
|
|
@@ -138,6 +138,12 @@ class Money
|
|
|
138
138
|
# Money.ca_dollar(570).format(html: true, with_currency: true)
|
|
139
139
|
# #=> "$5.70 <span class=\"currency\">CAD</span>"
|
|
140
140
|
#
|
|
141
|
+
# @option rules [Boolean] :html_wrap (false) Whether all currency parts should be HTML-formatted.
|
|
142
|
+
#
|
|
143
|
+
# @example
|
|
144
|
+
# Money.ca_dollar(570).format(html_wrap: true, with_currency: true)
|
|
145
|
+
# #=> "<span class=\"money-currency-symbol\">$</span><span class=\"money-whole\">5</span><span class=\"money-decimal-mark\">.</span><span class=\"money-decimal\">70</span> <span class=\"money-currency\">CAD</span>"
|
|
146
|
+
#
|
|
141
147
|
# @option rules [Boolean] :sign_before_symbol (false) Whether the sign should be
|
|
142
148
|
# before the currency symbol.
|
|
143
149
|
#
|
|
@@ -209,6 +215,14 @@ class Money
|
|
|
209
215
|
def to_s
|
|
210
216
|
return free_text if show_free_text?
|
|
211
217
|
|
|
218
|
+
if rules[:html]
|
|
219
|
+
warn "[DEPRECATION] `html` is deprecated - use `html_wrap` instead. Please note that `html_wrap` will wrap all parts of currency and if you use `with_currency` option, currency element class changes from `currency` to `money-currency`."
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if rules[:html_wrap_symbol]
|
|
223
|
+
warn "[DEPRECATION] `html_wrap_symbol` is deprecated - use `html_wrap` instead. Please note that `html_wrap` will wrap all parts of currency."
|
|
224
|
+
end
|
|
225
|
+
|
|
212
226
|
whole_part, decimal_part = extract_whole_and_decimal_parts
|
|
213
227
|
|
|
214
228
|
# Format whole and decimal parts separately
|
|
@@ -216,7 +230,15 @@ class Money
|
|
|
216
230
|
whole_part = format_whole_part(whole_part)
|
|
217
231
|
|
|
218
232
|
# Assemble the final formatted amount
|
|
219
|
-
formatted = [
|
|
233
|
+
formatted = if rules[:html_wrap]
|
|
234
|
+
if decimal_part.nil?
|
|
235
|
+
html_wrap(whole_part, "whole")
|
|
236
|
+
else
|
|
237
|
+
[html_wrap(whole_part, "whole"), html_wrap(decimal_mark, "decimal-mark"), html_wrap(decimal_part, "decimal")].join
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
[whole_part, decimal_part].compact.join(decimal_mark)
|
|
241
|
+
end
|
|
220
242
|
|
|
221
243
|
sign = money.negative? ? '-' : ''
|
|
222
244
|
|
|
@@ -232,7 +254,11 @@ class Money
|
|
|
232
254
|
symbol_value = symbol_value_from(rules)
|
|
233
255
|
|
|
234
256
|
if symbol_value && !symbol_value.empty?
|
|
235
|
-
|
|
257
|
+
if rules[:html_wrap_symbol]
|
|
258
|
+
symbol_value = "<span class=\"currency_symbol\">#{symbol_value}</span>"
|
|
259
|
+
elsif rules[:html_wrap]
|
|
260
|
+
symbol_value = html_wrap(symbol_value, "currency-symbol")
|
|
261
|
+
end
|
|
236
262
|
symbol_position = symbol_position_from(rules)
|
|
237
263
|
|
|
238
264
|
formatted = if symbol_position == :before
|
|
@@ -248,9 +274,14 @@ class Money
|
|
|
248
274
|
|
|
249
275
|
if rules[:with_currency]
|
|
250
276
|
formatted << " "
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
277
|
+
|
|
278
|
+
if rules[:html]
|
|
279
|
+
formatted << "<span class=\"currency\">#{currency.to_s}</span>"
|
|
280
|
+
elsif rules[:html_wrap]
|
|
281
|
+
formatted << html_wrap(currency.to_s, "currency")
|
|
282
|
+
else
|
|
283
|
+
formatted << currency.to_s
|
|
284
|
+
end
|
|
254
285
|
end
|
|
255
286
|
formatted
|
|
256
287
|
end
|
|
@@ -282,6 +313,10 @@ class Money
|
|
|
282
313
|
money.zero? && rules[:display_free]
|
|
283
314
|
end
|
|
284
315
|
|
|
316
|
+
def html_wrap(string, class_name)
|
|
317
|
+
"<span class=\"money-#{class_name}\">#{string}</span>"
|
|
318
|
+
end
|
|
319
|
+
|
|
285
320
|
def free_text
|
|
286
321
|
rules[:display_free].respond_to?(:to_str) ? rules[:display_free] : 'free'
|
|
287
322
|
end
|
|
@@ -359,7 +394,7 @@ class Money
|
|
|
359
394
|
else
|
|
360
395
|
""
|
|
361
396
|
end
|
|
362
|
-
elsif rules[:html]
|
|
397
|
+
elsif rules[:html] || rules[:html_wrap]
|
|
363
398
|
currency.html_entity == '' ? currency.symbol : currency.html_entity
|
|
364
399
|
elsif rules[:disambiguate] && currency.disambiguate_symbol
|
|
365
400
|
currency.disambiguate_symbol
|
data/lib/money/version.rb
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
describe Money::Allocation do
|
|
4
|
+
describe 'given number as argument' do
|
|
5
|
+
it 'raises an error when invalid argument is given' do
|
|
6
|
+
expect { described_class.generate(100, 0) }.to raise_error(ArgumentError)
|
|
7
|
+
expect { described_class.generate(100, -1) }.to raise_error(ArgumentError)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context 'whole amounts' do
|
|
11
|
+
it 'returns the amount when 1 is given' do
|
|
12
|
+
expect(described_class.generate(100, 1)).to eq([100])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'splits the amount into equal parts' do
|
|
16
|
+
expect(described_class.generate(100, 2)).to eq([50, 50])
|
|
17
|
+
expect(described_class.generate(100, 4)).to eq([25, 25, 25, 25])
|
|
18
|
+
expect(described_class.generate(100, 5)).to eq([20, 20, 20, 20, 20])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'does not loose pennies' do
|
|
22
|
+
expect(described_class.generate(5, 2)).to eq([3, 2])
|
|
23
|
+
expect(described_class.generate(2, 3)).to eq([1, 1, 0])
|
|
24
|
+
expect(described_class.generate(100, 3)).to eq([34, 33, 33])
|
|
25
|
+
expect(described_class.generate(100, 6)).to eq([17, 17, 17, 17, 16, 16])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'fractional amounts' do
|
|
30
|
+
it 'returns the amount when 1 is given' do
|
|
31
|
+
expect(described_class.generate(BigDecimal(100), 1, false)).to eq([BigDecimal(100)])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'splits the amount into equal parts' do
|
|
35
|
+
expect(described_class.generate(BigDecimal(100), 2, false)).to eq([50, 50])
|
|
36
|
+
expect(described_class.generate(BigDecimal(100), 4, false)).to eq([25, 25, 25, 25])
|
|
37
|
+
expect(described_class.generate(BigDecimal(100), 5, false)).to eq([20, 20, 20, 20, 20])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'splits the amount into equal fractions' do
|
|
41
|
+
expect(described_class.generate(BigDecimal(5), 2, false)).to eq([2.5, 2.5])
|
|
42
|
+
expect(described_class.generate(BigDecimal(5), 4, false)).to eq([1.25, 1.25, 1.25, 1.25])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'handles splits into repeating decimals' do
|
|
46
|
+
amount = BigDecimal(100)
|
|
47
|
+
parts = described_class.generate(amount, 3, false)
|
|
48
|
+
|
|
49
|
+
# Rounding due to inconsistent BigDecimal size in ruby compared to jruby. In reality the
|
|
50
|
+
# first 2 elements will look like the last one with a '5' at the end, compensating for a
|
|
51
|
+
# missing fraction
|
|
52
|
+
expect(parts.map { |x| x.round(10) }).to eq([
|
|
53
|
+
BigDecimal('33.3333333333'),
|
|
54
|
+
BigDecimal('33.3333333333'),
|
|
55
|
+
BigDecimal('33.3333333333')
|
|
56
|
+
])
|
|
57
|
+
expect(parts.inject(0, :+)).to eq(amount)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe 'given array as argument' do
|
|
63
|
+
it 'raises an error when invalid argument is given' do
|
|
64
|
+
expect { described_class.generate(100, []) }.to raise_error(ArgumentError)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'whole amounts' do
|
|
68
|
+
it 'returns the amount when array contains only one element' do
|
|
69
|
+
expect(described_class.generate(100, [1])).to eq([100])
|
|
70
|
+
expect(described_class.generate(100, [5])).to eq([100])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'splits the amount into whole parts respecting the order' do
|
|
74
|
+
expect(described_class.generate(100, [1, 1])).to eq([50, 50])
|
|
75
|
+
expect(described_class.generate(100, [1, 1, 2])).to eq([25, 25, 50])
|
|
76
|
+
expect(described_class.generate(100, [7, 3])).to eq([70, 30])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'accepts floats as arguments' do
|
|
80
|
+
expect(described_class.generate(100, [1.0, 1.0])).to eq([50, 50])
|
|
81
|
+
expect(described_class.generate(100, [0.1, 0.1, 0.2])).to eq([25, 25, 50])
|
|
82
|
+
expect(described_class.generate(100, [0.07, 0.03])).to eq([70, 30])
|
|
83
|
+
expect(described_class.generate(10, [0.1, 0.2, 0.1])).to eq([3, 5, 2])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'does not loose pennies' do
|
|
87
|
+
expect(described_class.generate(10, [1, 1, 2])).to eq([3, 2, 5])
|
|
88
|
+
expect(described_class.generate(100, [1, 1, 1])).to eq([34, 33, 33])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context 'fractional amounts' do
|
|
93
|
+
it 'returns the amount when array contains only one element' do
|
|
94
|
+
expect(described_class.generate(BigDecimal(100), [1], false)).to eq([100])
|
|
95
|
+
expect(described_class.generate(BigDecimal(100), [5], false)).to eq([100])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'splits the amount into whole parts respecting the order' do
|
|
99
|
+
expect(described_class.generate(BigDecimal(100), [1, 1], false)).to eq([50, 50])
|
|
100
|
+
expect(described_class.generate(BigDecimal(100), [1, 1, 2], false)).to eq([25, 25, 50])
|
|
101
|
+
expect(described_class.generate(BigDecimal(100), [7, 3], false)).to eq([70, 30])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'splits the amount proportionally to the given parts' do
|
|
105
|
+
expect(described_class.generate(BigDecimal(10), [1, 1, 2], false)).to eq([2.5, 2.5, 5])
|
|
106
|
+
expect(described_class.generate(BigDecimal(7), [1, 1], false)).to eq([3.5, 3.5])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'keeps the class of the splits the same as given amount' do
|
|
110
|
+
# Note that whole_amount is false but result is whole values
|
|
111
|
+
expect(described_class.generate(10, [1, 1, 2], false)).to eq([3, 2, 5])
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'handles splits into repeating decimals' do
|
|
115
|
+
amount = BigDecimal(100)
|
|
116
|
+
parts = described_class.generate(amount, [1, 1, 1], false)
|
|
117
|
+
|
|
118
|
+
# Rounding due to inconsistent BigDecimal size in ruby compared to jruby. In reality the
|
|
119
|
+
# first 2 elements will look like the last one with a '5' at the end, compensating for a
|
|
120
|
+
# missing fraction
|
|
121
|
+
expect(parts.map { |x| x.round(10) }).to eq([
|
|
122
|
+
BigDecimal('33.3333333333'),
|
|
123
|
+
BigDecimal('33.3333333333'),
|
|
124
|
+
BigDecimal('33.3333333333')
|
|
125
|
+
])
|
|
126
|
+
expect(parts.inject(0, :+)).to eq(amount)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -7,26 +7,6 @@ describe Money::Constructors do
|
|
|
7
7
|
expect(Money.empty).to eq Money.new(0)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
it "memoizes the result" do
|
|
11
|
-
expect(Money.empty.object_id).to eq Money.empty.object_id
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "memoizes a result for each currency" do
|
|
15
|
-
expect(Money.empty(:cad).object_id).to eq Money.empty(:cad).object_id
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "doesn't allow money to be modified for a currency" do
|
|
19
|
-
expect(Money.empty).to be_frozen
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "always use Currency object to memoize Money objects" do
|
|
23
|
-
Money.instance_variable_set(:@empty, {})
|
|
24
|
-
expect {
|
|
25
|
-
Money.zero(:usd)
|
|
26
|
-
Money.zero
|
|
27
|
-
}.to_not raise_error
|
|
28
|
-
end
|
|
29
|
-
|
|
30
10
|
it "instantiates a subclass when inheritance is used" do
|
|
31
11
|
special_money_class = Class.new(Money)
|
|
32
12
|
expect(special_money_class.empty).to be_a special_money_class
|
|
@@ -268,6 +268,12 @@ describe Money, "formatting" do
|
|
|
268
268
|
output = money.format(html: true, no_cents: true)
|
|
269
269
|
expect(output).to eq "19 ₽"
|
|
270
270
|
end
|
|
271
|
+
|
|
272
|
+
it "doesn't incorrectly format HTML (html_wrap)" do
|
|
273
|
+
money = ::Money.new(1999, "RUB")
|
|
274
|
+
output = money.format(html_wrap: true, no_cents: true)
|
|
275
|
+
expect(output).to eq "<span class=\"money-whole\">19</span> <span class=\"money-currency-symbol\">₽</span>"
|
|
276
|
+
end
|
|
271
277
|
end
|
|
272
278
|
|
|
273
279
|
describe ":no_cents_if_whole option" do
|
|
@@ -488,6 +494,23 @@ describe Money, "formatting" do
|
|
|
488
494
|
end
|
|
489
495
|
end
|
|
490
496
|
|
|
497
|
+
describe ":html_wrap option" do
|
|
498
|
+
specify "(html_wrap: true) works as documented" do
|
|
499
|
+
string = Money.ca_dollar(570).format(html_wrap: true)
|
|
500
|
+
expect(string).to eq "<span class=\"money-currency-symbol\">$</span><span class=\"money-whole\">5</span><span class=\"money-decimal-mark\">.</span><span class=\"money-decimal\">70</span>"
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
specify "(html_wrap: true, with_currency: true)" do
|
|
504
|
+
string = Money.ca_dollar(570).format(html_wrap: true, with_currency: true)
|
|
505
|
+
expect(string).to eq "<span class=\"money-currency-symbol\">$</span><span class=\"money-whole\">5</span><span class=\"money-decimal-mark\">.</span><span class=\"money-decimal\">70</span> <span class=\"money-currency\">CAD</span>"
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
specify "should fallback to symbol if entity is not available" do
|
|
509
|
+
string = Money.new(570, 'DKK').format(html_wrap: true)
|
|
510
|
+
expect(string).to eq "<span class=\"money-whole\">5</span><span class=\"money-decimal-mark\">,</span><span class=\"money-decimal\">70</span> <span class=\"money-currency-symbol\">kr.</span>"
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
491
514
|
describe ":symbol_position option" do
|
|
492
515
|
it "inserts currency symbol before the amount when set to :before" do
|
|
493
516
|
expect(Money.euro(1_234_567_12).format(symbol_position: :before)).to eq "€1.234.567,12"
|
data/spec/money_spec.rb
CHANGED
|
@@ -568,6 +568,25 @@ YAML
|
|
|
568
568
|
end
|
|
569
569
|
end
|
|
570
570
|
|
|
571
|
+
describe "#with_currency" do
|
|
572
|
+
it 'returns self if currency is the same' do
|
|
573
|
+
money = Money.new(10_00, 'USD')
|
|
574
|
+
|
|
575
|
+
expect(money.with_currency('USD')).to eq(money)
|
|
576
|
+
expect(money.with_currency('USD').object_id).to eq(money.object_id)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
it 'returns a new instance in a given currency' do
|
|
580
|
+
money = Money.new(10_00, 'USD')
|
|
581
|
+
new_money = money.with_currency('EUR')
|
|
582
|
+
|
|
583
|
+
expect(new_money).to eq(Money.new(10_00, 'EUR'))
|
|
584
|
+
expect(money.fractional).to eq(new_money.fractional)
|
|
585
|
+
expect(money.bank).to eq(new_money.bank)
|
|
586
|
+
expect(money.object_id).not_to eq(new_money.object_id)
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
571
590
|
describe "#exchange_to" do
|
|
572
591
|
it "exchanges the amount via its exchange bank" do
|
|
573
592
|
money = Money.new(100_00, "USD")
|
|
@@ -668,12 +687,8 @@ YAML
|
|
|
668
687
|
|
|
669
688
|
context "with infinite_precision", :infinite_precision do
|
|
670
689
|
it "allows for fractional cents allocation" do
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
moneys = Money.new(100).allocate([one_third, one_third, one_third])
|
|
674
|
-
expect(moneys[0].cents).to eq one_third * BigDecimal("100")
|
|
675
|
-
expect(moneys[1].cents).to eq one_third * BigDecimal("100")
|
|
676
|
-
expect(moneys[2].cents).to eq one_third * BigDecimal("100")
|
|
690
|
+
moneys = Money.new(100).allocate([1, 1, 1])
|
|
691
|
+
expect(moneys.inject(0, :+)).to eq(Money.new(100))
|
|
677
692
|
end
|
|
678
693
|
end
|
|
679
694
|
end
|
|
@@ -710,27 +725,40 @@ YAML
|
|
|
710
725
|
|
|
711
726
|
context "with infinite_precision", :infinite_precision do
|
|
712
727
|
it "allows for splitting by fractional cents" do
|
|
713
|
-
thirty_three_and_one_third = BigDecimal("100") / BigDecimal("3")
|
|
714
|
-
|
|
715
728
|
moneys = Money.new(100).split(3)
|
|
716
|
-
expect(moneys
|
|
717
|
-
expect(moneys[1].cents).to eq thirty_three_and_one_third
|
|
718
|
-
expect(moneys[2].cents).to eq thirty_three_and_one_third
|
|
729
|
+
expect(moneys.inject(0, :+)).to eq(Money.new(100))
|
|
719
730
|
end
|
|
720
731
|
end
|
|
721
732
|
end
|
|
722
733
|
|
|
723
734
|
describe "#round" do
|
|
724
|
-
|
|
725
735
|
let(:money) { Money.new(15.75, 'NZD') }
|
|
726
736
|
subject(:rounded) { money.round }
|
|
727
737
|
|
|
728
738
|
context "without infinite_precision" do
|
|
729
|
-
it "returns
|
|
730
|
-
rounded
|
|
731
|
-
|
|
739
|
+
it "returns a different money" do
|
|
740
|
+
expect(rounded).not_to be money
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
it "rounds the cents" do
|
|
732
744
|
expect(rounded.cents).to eq 16
|
|
733
745
|
end
|
|
746
|
+
|
|
747
|
+
it "maintains the currency" do
|
|
748
|
+
expect(rounded.currency).to eq Money::Currency.new('NZD')
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
it "uses a provided rounding strategy" do
|
|
752
|
+
rounded = money.round(BigDecimal::ROUND_DOWN)
|
|
753
|
+
expect(rounded.cents).to eq 15
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
it "does not accumulate rounding error" do
|
|
757
|
+
money_1 = Money.new(10.9).round(BigDecimal::ROUND_DOWN)
|
|
758
|
+
money_2 = Money.new(10.9).round(BigDecimal::ROUND_DOWN)
|
|
759
|
+
|
|
760
|
+
expect(money_1 + money_2).to eq(Money.new(20))
|
|
761
|
+
end
|
|
734
762
|
end
|
|
735
763
|
|
|
736
764
|
context "with infinite_precision", :infinite_precision do
|
|
@@ -751,15 +779,6 @@ YAML
|
|
|
751
779
|
expect(rounded.cents).to eq 15
|
|
752
780
|
end
|
|
753
781
|
|
|
754
|
-
context "when using a subclass of Money" do
|
|
755
|
-
let(:special_money_class) { Class.new(Money) }
|
|
756
|
-
let(:money) { special_money_class.new(15.75, 'NZD') }
|
|
757
|
-
|
|
758
|
-
it "preserves the class in the result" do
|
|
759
|
-
expect(rounded).to be_a special_money_class
|
|
760
|
-
end
|
|
761
|
-
end
|
|
762
|
-
|
|
763
782
|
context "when using a specific rounding precision" do
|
|
764
783
|
let(:money) { Money.new(15.7526, 'NZD') }
|
|
765
784
|
|
|
@@ -769,6 +788,22 @@ YAML
|
|
|
769
788
|
end
|
|
770
789
|
end
|
|
771
790
|
end
|
|
791
|
+
|
|
792
|
+
it 'preserves assigned bank' do
|
|
793
|
+
bank = Money::Bank::VariableExchange.new
|
|
794
|
+
rounded = Money.new(1_00, 'USD', bank).round
|
|
795
|
+
|
|
796
|
+
expect(rounded.bank).to eq(bank)
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
context "when using a subclass of Money" do
|
|
800
|
+
let(:special_money_class) { Class.new(Money) }
|
|
801
|
+
let(:money) { special_money_class.new(15.75, 'NZD') }
|
|
802
|
+
|
|
803
|
+
it "preserves the class in the result" do
|
|
804
|
+
expect(rounded).to be_a special_money_class
|
|
805
|
+
end
|
|
806
|
+
end
|
|
772
807
|
end
|
|
773
808
|
|
|
774
809
|
describe "#inspect" do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: money
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.
|
|
4
|
+
version: 6.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shane Emmons
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-07-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: i18n
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/money/currency/heuristics.rb
|
|
130
130
|
- lib/money/currency/loader.rb
|
|
131
131
|
- lib/money/money.rb
|
|
132
|
+
- lib/money/money/allocation.rb
|
|
132
133
|
- lib/money/money/arithmetic.rb
|
|
133
134
|
- lib/money/money/constructors.rb
|
|
134
135
|
- lib/money/money/formatter.rb
|
|
@@ -142,6 +143,7 @@ files:
|
|
|
142
143
|
- spec/currency/heuristics_spec.rb
|
|
143
144
|
- spec/currency/loader_spec.rb
|
|
144
145
|
- spec/currency_spec.rb
|
|
146
|
+
- spec/money/allocation_spec.rb
|
|
145
147
|
- spec/money/arithmetic_spec.rb
|
|
146
148
|
- spec/money/constructors_spec.rb
|
|
147
149
|
- spec/money/formatting_spec.rb
|
|
@@ -180,6 +182,7 @@ test_files:
|
|
|
180
182
|
- spec/currency/heuristics_spec.rb
|
|
181
183
|
- spec/currency/loader_spec.rb
|
|
182
184
|
- spec/currency_spec.rb
|
|
185
|
+
- spec/money/allocation_spec.rb
|
|
183
186
|
- spec/money/arithmetic_spec.rb
|
|
184
187
|
- spec/money/constructors_spec.rb
|
|
185
188
|
- spec/money/formatting_spec.rb
|