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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1676f3c1841250eb391b20a12e2ae1701455c87b
4
- data.tar.gz: c0ed79f93a51427b43c6af3bd3094219dbd69fe8
3
+ metadata.gz: 833d36d87efd793fa9e73986df700da145b59578
4
+ data.tar.gz: 5fae93b474a739c79e45c18e45b3d652c4f1d4d6
5
5
  SHA512:
6
- metadata.gz: cb4a7fa2426f81cfc97fa07e983f12a4427ed891f2f6e2403865e58011c61ecd2244d12c2294d858f731707ae96b41a31e9bfbeb61b49006532bfb881fc6a190
7
- data.tar.gz: ffaf8bc1eb9cb35d5c5d2017c5f4483d4b8b6ab1decc01ea0803fbb101ef053c3df4f37c8c4706ab7b6c5606aa5e3b8531fcfb9eeef4a626d29d715400b1ff40
6
+ metadata.gz: 77560afc19c7447c654daa551924ee3a0cd84e17a880d64b9d012aefda8fabc23ea100ad229278937817025f986839d9c6112c04cd8b1ee862ca0faf02fe9b4c
7
+ data.tar.gz: 8c126aadb1cd5beedff854a8353a46a423296ceccdc8e1f72b7c65016937df8cb643c78c735fa87327bb41838ff12a76919a561e35ad5d3cb3eeca2573b5f526
@@ -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"
@@ -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
- Numeric === amount or raise ArgumentError, "'amount' must be numeric"
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
- # Conversation to +self+.
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
- # Allocates money between different parties without losing pennies.
467
- # After the mathematical split has been performed, leftover pennies will
468
- # be distributed round-robin amongst the parties. This means that parties
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>] splits [2, 1, 1] to give twice as much to party1 as party2 or party3
472
- # which results in 50% of the cash to party1, 25% to party2, and 25% to party3.
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([0.3, 0.7]) #=> [Money.new(2), Money.new(3)]
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(splits)
481
- amounts, left_over = amounts_from_splits(splits)
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
- if self.class.infinite_precision
527
- self.class.new(fractional.round(rounding_precision, rounding_mode), self.currency)
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
- @empty ||= {}
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 = [whole_part, decimal_part].compact.join(decimal_mark)
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
- symbol_value = "<span class=\"currency_symbol\">#{symbol_value}</span>" if rules[:html_wrap_symbol]
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
- formatted << '<span class="currency">' if rules[:html]
252
- formatted << currency.to_s
253
- formatted << '</span>' if rules[:html]
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
@@ -1,3 +1,3 @@
1
1
  class Money
2
- VERSION = '6.11.3'
2
+ VERSION = '6.12.0'
3
3
  end
@@ -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 &#x20BD;"
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\">&#x20BD;</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"
@@ -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
- one_third = BigDecimal("1") / BigDecimal("3")
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[0].cents).to eq thirty_three_and_one_third
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 self (as it is already rounded)" do
730
- rounded = money.round
731
- expect(rounded).to be money
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.11.3
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-05-09 00:00:00.000000000 Z
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