money 6.11.3 → 6.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|