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 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