money 6.13.0 → 6.13.8

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.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +49 -2
  3. data/README.md +93 -3
  4. data/config/currency_backwards_compatible.json +31 -0
  5. data/config/currency_iso.json +18 -18
  6. data/lib/money/bank/variable_exchange.rb +14 -6
  7. data/lib/money/currency.rb +4 -4
  8. data/lib/money/currency/loader.rb +15 -13
  9. data/lib/money/locale_backend/currency.rb +11 -0
  10. data/lib/money/locale_backend/i18n.rb +2 -1
  11. data/lib/money/locale_backend/legacy.rb +2 -2
  12. data/lib/money/money.rb +100 -52
  13. data/lib/money/money/allocation.rb +8 -5
  14. data/lib/money/money/arithmetic.rb +20 -10
  15. data/lib/money/money/formatter.rb +3 -0
  16. data/lib/money/money/formatting_rules.rb +15 -5
  17. data/lib/money/money/locale_backend.rb +3 -1
  18. data/lib/money/rates_store/memory.rb +24 -23
  19. data/lib/money/version.rb +1 -1
  20. data/money.gemspec +4 -4
  21. metadata +11 -51
  22. data/.coveralls.yml +0 -1
  23. data/.gitignore +0 -23
  24. data/.rspec +0 -2
  25. data/.travis.yml +0 -32
  26. data/AUTHORS +0 -131
  27. data/CONTRIBUTING.md +0 -17
  28. data/Gemfile +0 -16
  29. data/Rakefile +0 -17
  30. data/spec/bank/base_spec.rb +0 -79
  31. data/spec/bank/single_currency_spec.rb +0 -13
  32. data/spec/bank/variable_exchange_spec.rb +0 -265
  33. data/spec/currency/heuristics_spec.rb +0 -11
  34. data/spec/currency/loader_spec.rb +0 -19
  35. data/spec/currency_spec.rb +0 -400
  36. data/spec/locale_backend/i18n_spec.rb +0 -62
  37. data/spec/locale_backend/legacy_spec.rb +0 -74
  38. data/spec/money/allocation_spec.rb +0 -130
  39. data/spec/money/arithmetic_spec.rb +0 -756
  40. data/spec/money/constructors_spec.rb +0 -91
  41. data/spec/money/formatting_spec.rb +0 -855
  42. data/spec/money/locale_backend_spec.rb +0 -14
  43. data/spec/money_spec.rb +0 -880
  44. data/spec/rates_store/memory_spec.rb +0 -80
  45. data/spec/spec_helper.rb +0 -30
  46. data/spec/support/shared_examples/money_examples.rb +0 -14
@@ -1,62 +0,0 @@
1
- # encoding: utf-8
2
-
3
- describe Money::LocaleBackend::I18n do
4
- describe '#initialize' do
5
- it 'raises an error when I18n is not defined' do
6
- hide_const('I18n')
7
-
8
- expect { described_class.new }.to raise_error(Money::LocaleBackend::NotSupported)
9
- end
10
- end
11
-
12
- describe '#lookup' do
13
- after do
14
- reset_i18n
15
- I18n.locale = :en
16
- end
17
-
18
- subject { described_class.new }
19
-
20
- context 'with number.currency.format defined' do
21
- before do
22
- I18n.locale = :de
23
- I18n.backend.store_translations(:de, number: {
24
- currency: { format: { delimiter: '.', separator: ',' } }
25
- })
26
- end
27
-
28
- it 'returns thousands_separator based on the current locale' do
29
- expect(subject.lookup(:thousands_separator, nil)).to eq('.')
30
- end
31
-
32
- it 'returns decimal_mark based on the current locale' do
33
- expect(subject.lookup(:decimal_mark, nil)).to eq(',')
34
- end
35
- end
36
-
37
- context 'with number.format defined' do
38
- before do
39
- I18n.locale = :de
40
- I18n.backend.store_translations(:de, number: { format: { delimiter: '.', separator: ',' } })
41
- end
42
-
43
- it 'returns thousands_separator based on the current locale' do
44
- expect(subject.lookup(:thousands_separator, nil)).to eq('.')
45
- end
46
-
47
- it 'returns decimal_mark based on the current locale' do
48
- expect(subject.lookup(:decimal_mark, nil)).to eq(',')
49
- end
50
- end
51
-
52
- context 'with no translation defined' do
53
- it 'returns thousands_separator based on the current locale' do
54
- expect(subject.lookup(:thousands_separator, nil)).to eq(nil)
55
- end
56
-
57
- it 'returns decimal_mark based on the current locale' do
58
- expect(subject.lookup(:decimal_mark, nil)).to eq(nil)
59
- end
60
- end
61
- end
62
- end
@@ -1,74 +0,0 @@
1
- # encoding: utf-8
2
-
3
- describe Money::LocaleBackend::Legacy do
4
- after { Money.use_i18n = true }
5
-
6
- describe '#initialize' do
7
- it 'raises an error when use_i18n is true and I18n is not defined' do
8
- Money.use_i18n = true
9
- hide_const('I18n')
10
-
11
- expect { described_class.new }.to raise_error(Money::LocaleBackend::NotSupported)
12
- end
13
-
14
- it 'does not raise error when use_i18n is false and I18n is not defined' do
15
- Money.use_i18n = false
16
- hide_const('I18n')
17
-
18
- expect { described_class.new }.not_to raise_error
19
- end
20
- end
21
-
22
- describe '#lookup' do
23
- subject { described_class.new }
24
- let(:currency) { Money::Currency.new('USD') }
25
-
26
- context 'use_i18n is true and i18n lookup is successful' do
27
- before do
28
- allow(subject.send(:i18n_backend))
29
- .to receive(:lookup)
30
- .with(:thousands_separator, nil)
31
- .and_return('.')
32
-
33
- allow(subject.send(:i18n_backend))
34
- .to receive(:lookup)
35
- .with(:decimal_mark, nil)
36
- .and_return(',')
37
- end
38
-
39
- it 'returns thousands_separator from I18n' do
40
- expect(subject.lookup(:thousands_separator, currency)).to eq('.')
41
- end
42
-
43
- it 'returns decimal_mark based from I18n' do
44
- expect(subject.lookup(:decimal_mark, currency)).to eq(',')
45
- end
46
- end
47
-
48
- context 'use_i18n is true but i18n lookup is unsuccessful' do
49
- before do
50
- allow(subject.send(:i18n_backend)).to receive(:lookup).and_return(nil)
51
- end
52
-
53
- it 'returns thousands_separator as defined in currency' do
54
- expect(subject.lookup(:thousands_separator, currency)).to eq(',')
55
- end
56
-
57
- it 'returns decimal_mark based as defined in currency' do
58
- expect(subject.lookup(:decimal_mark, currency)).to eq('.')
59
- end
60
- end
61
-
62
- context 'use_i18n is false' do
63
- before { Money.use_i18n = false }
64
-
65
- it 'returns thousands_separator as defined in currency' do
66
- expect(subject.lookup(:thousands_separator, currency)).to eq(',')
67
- end
68
-
69
- it 'returns decimal_mark based as defined in currency' do
70
- expect(subject.lookup(:decimal_mark, currency)).to eq('.')
71
- end
72
- end
73
- end
74
- end
@@ -1,130 +0,0 @@
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
@@ -1,756 +0,0 @@
1
- # encoding: utf-8
2
-
3
- describe Money do
4
- describe "-@" do
5
- it "changes the sign of a number" do
6
- expect((- Money.new(0))).to eq Money.new(0)
7
- expect((- Money.new(1))).to eq Money.new(-1)
8
- expect((- Money.new(-1))).to eq Money.new(1)
9
- end
10
-
11
- it "preserves the class in the result when using a subclass of Money" do
12
- special_money_class = Class.new(Money)
13
- expect(- special_money_class.new(10_00)).to be_a special_money_class
14
- end
15
-
16
- it_behaves_like 'instance with custom bank', :-@
17
- end
18
-
19
- describe "#==" do
20
- it "returns true if both the amounts and currencies are equal" do
21
- expect(Money.new(1_00, "USD")).to eq Money.new(1_00, "USD")
22
- expect(Money.new(1_00, "USD")).not_to eq Money.new(1_00, "EUR")
23
- expect(Money.new(1_00, "USD")).not_to eq Money.new(2_00, "USD")
24
- expect(Money.new(1_00, "USD")).not_to eq Money.new(99_00, "EUR")
25
- end
26
-
27
- it "returns true if both amounts are zero, even if currency differs" do
28
- expect(Money.new(0, "USD")).to eq Money.new(0, "USD")
29
- expect(Money.new(0, "USD")).to eq Money.new(0, "EUR")
30
- expect(Money.new(0, "USD")).to eq Money.new(0, "AUD")
31
- expect(Money.new(0, "USD")).to eq Money.new(0, "JPY")
32
- end
33
-
34
- it "returns false if used to compare with an object that doesn't inherit from Money" do
35
- expect(Money.new(1_00, "USD")).not_to eq Object.new
36
- expect(Money.new(1_00, "USD")).not_to eq Class
37
- expect(Money.new(1_00, "USD")).not_to eq Kernel
38
- expect(Money.new(1_00, "USD")).not_to eq(/foo/)
39
- expect(Money.new(1_00, "USD")).not_to eq nil
40
- end
41
-
42
- it "can be used to compare with an object that inherits from Money" do
43
- klass = Class.new(Money)
44
-
45
- expect(Money.new(1_00, "USD")).to eq klass.new(1_00, "USD")
46
- expect(Money.new(2_50, "USD")).to eq klass.new(2_50, "USD")
47
- expect(Money.new(2_50, "USD")).not_to eq klass.new(3_00, "USD")
48
- expect(Money.new(1_00, "GBP")).not_to eq klass.new(1_00, "USD")
49
- end
50
-
51
- it 'allows comparison with zero' do
52
- expect(Money.new(0, :usd)).to eq 0
53
- expect(Money.new(0, :usd)).to eq 0.0
54
- expect(Money.new(0, :usd)).to eq BigDecimal(0)
55
- expect(Money.new(1, :usd)).to_not eq 0
56
- end
57
-
58
- it 'raises error for non-zero numerics' do
59
- expect { Money.new(1_00, :usd) == 1 }.to raise_error ArgumentError
60
- expect { Money.new(1_00, :usd) == -2.0 }.to raise_error ArgumentError
61
- expect { Money.new(1_00, :usd) == Float::INFINITY }.to raise_error ArgumentError
62
- end
63
- end
64
-
65
- describe "#eql?" do
66
- it "returns true if and only if their amount and currency are equal" do
67
- expect(Money.new(1_00, "USD").eql?(Money.new(1_00, "USD"))).to be true
68
- expect(Money.new(1_00, "USD").eql?(Money.new(1_00, "EUR"))).to be false
69
- expect(Money.new(1_00, "USD").eql?(Money.new(2_00, "USD"))).to be false
70
- expect(Money.new(1_00, "USD").eql?(Money.new(99_00, "EUR"))).to be false
71
- end
72
-
73
- it "returns true when their amounts are zero and currencies differ" do
74
- expect(Money.new(0, "USD").eql?(Money.new(0, "EUR"))).to be true
75
- expect(Money.new(0, "USD").eql?(Money.new(0, "USD"))).to be true
76
- expect(Money.new(0, "AUD").eql?(Money.new(0, "EUR"))).to be true
77
- end
78
-
79
- it "returns false if used to compare with an object that doesn't inherit from Money" do
80
- expect(Money.new(1_00, "USD").eql?(Object.new)).to be false
81
- expect(Money.new(1_00, "USD").eql?(Class)).to be false
82
- expect(Money.new(1_00, "USD").eql?(Kernel)).to be false
83
- expect(Money.new(1_00, "USD").eql?(/foo/)).to be false
84
- expect(Money.new(1_00, "USD").eql?(nil)).to be false
85
- end
86
-
87
- it "can be used to compare with an object that inherits from Money" do
88
- klass = Class.new(Money)
89
-
90
- expect(Money.new(1_00, "USD").eql?(klass.new(1_00, "USD"))).to be true
91
- expect(Money.new(2_50, "USD").eql?(klass.new(2_50, "USD"))).to be true
92
- expect(Money.new(2_50, "USD").eql?(klass.new(3_00, "USD"))).to be false
93
- expect(Money.new(1_00, "GBP").eql?(klass.new(1_00, "USD"))).to be false
94
- end
95
- end
96
-
97
- describe "#<=>" do
98
- it "compares the two object amounts (same currency)" do
99
- expect((Money.new(1_00, "USD") <=> Money.new(1_00, "USD"))).to eq 0
100
- expect((Money.new(1_00, "USD") <=> Money.new(99, "USD"))).to be > 0
101
- expect((Money.new(1_00, "USD") <=> Money.new(2_00, "USD"))).to be < 0
102
- end
103
-
104
- it "converts other object amount to current currency, then compares the two object amounts (different currency)" do
105
- target = Money.new(200_00, "EUR")
106
- expect(target).to receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(300_00, "USD"))
107
- expect(Money.new(100_00, "USD") <=> target).to be < 0
108
-
109
- target = Money.new(200_00, "EUR")
110
- expect(target).to receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(100_00, "USD"))
111
- expect(Money.new(100_00, "USD") <=> target).to eq 0
112
-
113
- target = Money.new(200_00, "EUR")
114
- expect(target).to receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(99_00, "USD"))
115
- expect(Money.new(100_00, "USD") <=> target).to be > 0
116
- end
117
-
118
- it "returns nil if currency conversion fails, and therefore cannot be compared" do
119
- target = Money.new(200_00, "EUR")
120
- expect(target).to receive(:exchange_to).with(Money::Currency.new("USD")).and_raise(Money::Bank::UnknownRate)
121
- expect(Money.new(100_00, "USD") <=> target).to be_nil
122
- end
123
-
124
- it "can be used to compare with an object that inherits from Money" do
125
- klass = Class.new(Money)
126
-
127
- expect(Money.new(1_00) <=> klass.new(1_00)).to eq 0
128
- expect(Money.new(1_00) <=> klass.new(99)).to be > 0
129
- expect(Money.new(1_00) <=> klass.new(2_00)).to be < 0
130
- end
131
-
132
- it "returns nill when comparing with an object that doesn't inherit from Money" do
133
- expect(Money.new(1_00) <=> 100).to be_nil
134
- expect(Money.new(1_00) <=> Object.new).to be_nil
135
- expect(Money.new(1_00) <=> Class).to be_nil
136
- expect(Money.new(1_00) <=> Kernel).to be_nil
137
- expect(Money.new(1_00) <=> /foo/).to be_nil
138
- end
139
-
140
- context 'when conversions disallowed' do
141
- around do |example|
142
- begin
143
- old_default_bank = Money.default_bank
144
- Money.disallow_currency_conversion!
145
- example.run
146
- ensure
147
- Money.default_bank = old_default_bank
148
- end
149
- end
150
-
151
- context 'when currencies differ' do
152
- context 'when both values are 1_00' do
153
- it 'raises currency error' do
154
- expect { Money.usd(1_00) <=> Money.gbp(1_00) }.to raise_error Money::Bank::DifferentCurrencyError
155
- end
156
- end
157
-
158
- context 'when both values are 0' do
159
- it 'considers them equal' do
160
- expect(Money.usd(0) <=> Money.gbp(0)).to eq(0)
161
- end
162
- end
163
- end
164
- end
165
-
166
- it 'compares with numeric 0' do
167
- expect(Money.usd(1) < 0).to eq false
168
- expect(Money.usd(1) > 0.0).to eq true
169
- expect(Money.usd(0) >= 0.0).to eq true
170
- end
171
- end
172
-
173
- describe "#positive?" do
174
- it "returns true if the amount is greater than 0" do
175
- expect(Money.new(1)).to be_positive
176
- end
177
-
178
- it "returns false if the amount is 0" do
179
- expect(Money.new(0)).not_to be_positive
180
- end
181
-
182
- it "returns false if the amount is negative" do
183
- expect(Money.new(-1)).not_to be_positive
184
- end
185
- end
186
-
187
- describe "#negative?" do
188
- it "returns true if the amount is less than 0" do
189
- expect(Money.new(-1)).to be_negative
190
- end
191
-
192
- it "returns false if the amount is 0" do
193
- expect(Money.new(0)).not_to be_negative
194
- end
195
-
196
- it "returns false if the amount is greater than 0" do
197
- expect(Money.new(1)).not_to be_negative
198
- end
199
- end
200
-
201
- describe "#+" do
202
- it "adds other amount to current amount (same currency)" do
203
- expect(Money.new(10_00, "USD") + Money.new(90, "USD")).to eq Money.new(10_90, "USD")
204
- end
205
-
206
- it "converts other object amount to current currency and adds other amount to current amount (different currency)" do
207
- other = Money.new(90, "EUR")
208
- expect(other).to receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(9_00, "USD"))
209
- expect(Money.new(10_00, "USD") + other).to eq Money.new(19_00, "USD")
210
- end
211
-
212
- it "adds Integer 0 to money and returns the same ammount" do
213
- expect(Money.new(10_00) + 0).to eq Money.new(10_00)
214
- end
215
-
216
- it "preserves the class in the result when using a subclass of Money" do
217
- special_money_class = Class.new(Money)
218
- expect(special_money_class.new(10_00, "USD") + Money.new(90, "USD")).to be_a special_money_class
219
- end
220
-
221
- it_behaves_like 'instance with custom bank', :+, Money.new(1)
222
- end
223
-
224
- describe "#-" do
225
- it "subtracts other amount from current amount (same currency)" do
226
- expect(Money.new(10_00, "USD") - Money.new(90, "USD")).to eq Money.new(9_10, "USD")
227
- end
228
-
229
- it "converts other object amount to current currency and subtracts other amount from current amount (different currency)" do
230
- other = Money.new(90, "EUR")
231
- expect(other).to receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(9_00, "USD"))
232
- expect(Money.new(10_00, "USD") - other).to eq Money.new(1_00, "USD")
233
- end
234
-
235
- it "subtract Integer 0 to money and returns the same ammount" do
236
- expect(Money.new(10_00) - 0).to eq Money.new(10_00)
237
- end
238
-
239
- it "preserves the class in the result when using a subclass of Money" do
240
- special_money_class = Class.new(Money)
241
- expect(special_money_class.new(10_00, "USD") - Money.new(90, "USD")).to be_a special_money_class
242
- end
243
-
244
- it_behaves_like 'instance with custom bank', :-, Money.new(1)
245
- end
246
-
247
- describe "#*" do
248
- it "multiplies Money by Integer and returns Money" do
249
- ts = [
250
- {a: Money.new( 10, :USD), b: 4, c: Money.new( 40, :USD)},
251
- {a: Money.new( 10, :USD), b: -4, c: Money.new(-40, :USD)},
252
- {a: Money.new(-10, :USD), b: 4, c: Money.new(-40, :USD)},
253
- {a: Money.new(-10, :USD), b: -4, c: Money.new( 40, :USD)},
254
- ]
255
- ts.each do |t|
256
- expect(t[:a] * t[:b]).to eq t[:c]
257
- end
258
- end
259
-
260
- it "does not multiply Money by Money (same currency)" do
261
- expect { Money.new(10, :USD) * Money.new(4, :USD) }.to raise_error(TypeError)
262
- end
263
-
264
- it "does not multiply Money by Money (different currency)" do
265
- expect { Money.new(10, :USD) * Money.new(4, :EUR) }.to raise_error(TypeError)
266
- end
267
-
268
- it "does not multiply Money by an object which is NOT a number" do
269
- expect { Money.new(10, :USD) * 'abc' }.to raise_error(TypeError)
270
- end
271
-
272
- it "preserves the class in the result when using a subclass of Money" do
273
- special_money_class = Class.new(Money)
274
- expect(special_money_class.new(10_00, "USD") * 2).to be_a special_money_class
275
- end
276
-
277
- it_behaves_like 'instance with custom bank', :*, 1
278
- end
279
-
280
- describe "#/" do
281
- it "divides Money by Integer and returns Money" do
282
- ts = [
283
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 3, :USD)},
284
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3, :USD)},
285
- {a: Money.new(-13, :USD), b: 4, c: Money.new(-3, :USD)},
286
- {a: Money.new(-13, :USD), b: -4, c: Money.new( 3, :USD)},
287
- ]
288
- ts.each do |t|
289
- expect(t[:a] / t[:b]).to eq t[:c]
290
- end
291
- end
292
-
293
- it "preserves the class in the result when using a subclass of Money" do
294
- special_money_class = Class.new(Money)
295
- expect(special_money_class.new(10_00, "USD") / 2).to be_a special_money_class
296
- end
297
-
298
- context 'rounding preference' do
299
- before do
300
- allow(Money).to receive(:rounding_mode).and_return(rounding_mode)
301
- end
302
-
303
- after do
304
- allow(Money).to receive(:rounding_mode).and_call_original
305
- end
306
-
307
- context 'ceiling rounding' do
308
- let(:rounding_mode) { BigDecimal::ROUND_CEILING }
309
- it "obeys the rounding preference" do
310
- expect(Money.new(10) / 3).to eq Money.new(4)
311
- end
312
- end
313
-
314
- context 'floor rounding' do
315
- let(:rounding_mode) { BigDecimal::ROUND_FLOOR }
316
- it "obeys the rounding preference" do
317
- expect(Money.new(10) / 6).to eq Money.new(1)
318
- end
319
- end
320
-
321
- context 'half up rounding' do
322
- let(:rounding_mode) { BigDecimal::ROUND_HALF_UP }
323
- it "obeys the rounding preference" do
324
- expect(Money.new(10) / 4).to eq Money.new(3)
325
- end
326
- end
327
-
328
- context 'half down rounding' do
329
- let(:rounding_mode) { BigDecimal::ROUND_HALF_DOWN }
330
- it "obeys the rounding preference" do
331
- expect(Money.new(10) / 4).to eq Money.new(2)
332
- end
333
- end
334
- end
335
-
336
- it "divides Money by Money (same currency) and returns Float" do
337
- ts = [
338
- {a: Money.new( 13, :USD), b: Money.new( 4, :USD), c: 3.25},
339
- {a: Money.new( 13, :USD), b: Money.new(-4, :USD), c: -3.25},
340
- {a: Money.new(-13, :USD), b: Money.new( 4, :USD), c: -3.25},
341
- {a: Money.new(-13, :USD), b: Money.new(-4, :USD), c: 3.25},
342
- ]
343
- ts.each do |t|
344
- expect(t[:a] / t[:b]).to eq t[:c]
345
- end
346
- end
347
-
348
- it "divides Money by Money (different currency) and returns Float" do
349
- ts = [
350
- {a: Money.new( 13, :USD), b: Money.new( 4, :EUR), c: 1.625},
351
- {a: Money.new( 13, :USD), b: Money.new(-4, :EUR), c: -1.625},
352
- {a: Money.new(-13, :USD), b: Money.new( 4, :EUR), c: -1.625},
353
- {a: Money.new(-13, :USD), b: Money.new(-4, :EUR), c: 1.625},
354
- ]
355
- ts.each do |t|
356
- expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
357
- expect(t[:a] / t[:b]).to eq t[:c]
358
- end
359
- end
360
-
361
- context "with infinite_precision", :infinite_precision do
362
- it "uses BigDecimal division" do
363
- ts = [
364
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 3.25, :USD)},
365
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3.25, :USD)},
366
- {a: Money.new(-13, :USD), b: 4, c: Money.new(-3.25, :USD)},
367
- {a: Money.new(-13, :USD), b: -4, c: Money.new( 3.25, :USD)},
368
- ]
369
- ts.each do |t|
370
- expect(t[:a] / t[:b]).to eq t[:c]
371
- end
372
- end
373
- end
374
-
375
- it_behaves_like 'instance with custom bank', :/, 1
376
- end
377
-
378
- describe "#div" do
379
- it "divides Money by Integer and returns Money" do
380
- ts = [
381
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 3, :USD)},
382
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3, :USD)},
383
- {a: Money.new(-13, :USD), b: 4, c: Money.new(-3, :USD)},
384
- {a: Money.new(-13, :USD), b: -4, c: Money.new( 3, :USD)},
385
- ]
386
- ts.each do |t|
387
- expect(t[:a].div(t[:b])).to eq t[:c]
388
- end
389
- end
390
-
391
- it "divides Money by Money (same currency) and returns Float" do
392
- ts = [
393
- {a: Money.new( 13, :USD), b: Money.new( 4, :USD), c: 3.25},
394
- {a: Money.new( 13, :USD), b: Money.new(-4, :USD), c: -3.25},
395
- {a: Money.new(-13, :USD), b: Money.new( 4, :USD), c: -3.25},
396
- {a: Money.new(-13, :USD), b: Money.new(-4, :USD), c: 3.25},
397
- ]
398
- ts.each do |t|
399
- expect(t[:a].div(t[:b])).to eq t[:c]
400
- end
401
- end
402
-
403
- it "divides Money by Money (different currency) and returns Float" do
404
- ts = [
405
- {a: Money.new( 13, :USD), b: Money.new( 4, :EUR), c: 1.625},
406
- {a: Money.new( 13, :USD), b: Money.new(-4, :EUR), c: -1.625},
407
- {a: Money.new(-13, :USD), b: Money.new( 4, :EUR), c: -1.625},
408
- {a: Money.new(-13, :USD), b: Money.new(-4, :EUR), c: 1.625},
409
- ]
410
- ts.each do |t|
411
- expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
412
- expect(t[:a].div(t[:b])).to eq t[:c]
413
- end
414
- end
415
-
416
- context "with infinite_precision", :infinite_precision do
417
- it "uses BigDecimal division" do
418
- ts = [
419
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 3.25, :USD)},
420
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3.25, :USD)},
421
- {a: Money.new(-13, :USD), b: 4, c: Money.new(-3.25, :USD)},
422
- {a: Money.new(-13, :USD), b: -4, c: Money.new( 3.25, :USD)},
423
- ]
424
- ts.each do |t|
425
- expect(t[:a].div(t[:b])).to eq t[:c]
426
- end
427
- end
428
- end
429
- end
430
-
431
- describe "#divmod" do
432
- it "calculates division and modulo with Integer" do
433
- ts = [
434
- {a: Money.new( 13, :USD), b: 4, c: [Money.new( 3, :USD), Money.new( 1, :USD)]},
435
- {a: Money.new( 13, :USD), b: -4, c: [Money.new(-4, :USD), Money.new(-3, :USD)]},
436
- {a: Money.new(-13, :USD), b: 4, c: [Money.new(-4, :USD), Money.new( 3, :USD)]},
437
- {a: Money.new(-13, :USD), b: -4, c: [Money.new( 3, :USD), Money.new(-1, :USD)]},
438
- ]
439
- ts.each do |t|
440
- expect(t[:a].divmod(t[:b])).to eq t[:c]
441
- end
442
- end
443
-
444
- it "calculates division and modulo with Money (same currency)" do
445
- ts = [
446
- {a: Money.new( 13, :USD), b: Money.new( 4, :USD), c: [ 3, Money.new( 1, :USD)]},
447
- {a: Money.new( 13, :USD), b: Money.new(-4, :USD), c: [-4, Money.new(-3, :USD)]},
448
- {a: Money.new(-13, :USD), b: Money.new( 4, :USD), c: [-4, Money.new( 3, :USD)]},
449
- {a: Money.new(-13, :USD), b: Money.new(-4, :USD), c: [ 3, Money.new(-1, :USD)]},
450
- ]
451
- ts.each do |t|
452
- expect(t[:a].divmod(t[:b])).to eq t[:c]
453
- end
454
- end
455
-
456
- it "calculates division and modulo with Money (different currency)" do
457
- ts = [
458
- {a: Money.new( 13, :USD), b: Money.new( 4, :EUR), c: [ 1, Money.new( 5, :USD)]},
459
- {a: Money.new( 13, :USD), b: Money.new(-4, :EUR), c: [-2, Money.new(-3, :USD)]},
460
- {a: Money.new(-13, :USD), b: Money.new( 4, :EUR), c: [-2, Money.new( 3, :USD)]},
461
- {a: Money.new(-13, :USD), b: Money.new(-4, :EUR), c: [ 1, Money.new(-5, :USD)]},
462
- ]
463
- ts.each do |t|
464
- expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
465
- expect(t[:a].divmod(t[:b])).to eq t[:c]
466
- end
467
- end
468
-
469
- context "with infinite_precision", :infinite_precision do
470
- it "uses BigDecimal division" do
471
- ts = [
472
- {a: Money.new( 13, :USD), b: 4, c: [Money.new( 3, :USD), Money.new( 1, :USD)]},
473
- {a: Money.new( 13, :USD), b: -4, c: [Money.new(-4, :USD), Money.new(-3, :USD)]},
474
- {a: Money.new(-13, :USD), b: 4, c: [Money.new(-4, :USD), Money.new( 3, :USD)]},
475
- {a: Money.new(-13, :USD), b: -4, c: [Money.new( 3, :USD), Money.new(-1, :USD)]},
476
- ]
477
- ts.each do |t|
478
- expect(t[:a].divmod(t[:b])).to eq t[:c]
479
- end
480
- end
481
- end
482
-
483
- it "preserves the class in the result when dividing a subclass of Money by a fixnum" do
484
- special_money_class = Class.new(Money)
485
- expect(special_money_class.new(10_00, "USD").divmod(4).last).to be_a special_money_class
486
- end
487
-
488
- it "preserves the class in the result when using a subclass of Money by a subclass of Money" do
489
- special_money_class = Class.new(Money)
490
- expect(special_money_class.new(10_00, "USD").divmod(special_money_class.new(4_00)).last).to be_a special_money_class
491
- end
492
-
493
- it_behaves_like 'instance with custom bank', :divmod, Money.new(1)
494
- it_behaves_like 'instance with custom bank', :divmod, 1
495
- end
496
-
497
- describe "#modulo" do
498
- it "calculates modulo with Integer" do
499
- ts = [
500
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 1, :USD)},
501
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3, :USD)},
502
- {a: Money.new(-13, :USD), b: 4, c: Money.new( 3, :USD)},
503
- {a: Money.new(-13, :USD), b: -4, c: Money.new(-1, :USD)},
504
- ]
505
- ts.each do |t|
506
- expect(t[:a].modulo(t[:b])).to eq t[:c]
507
- end
508
- end
509
-
510
- it "calculates modulo with Money (same currency)" do
511
- ts = [
512
- {a: Money.new( 13, :USD), b: Money.new( 4, :USD), c: Money.new( 1, :USD)},
513
- {a: Money.new( 13, :USD), b: Money.new(-4, :USD), c: Money.new(-3, :USD)},
514
- {a: Money.new(-13, :USD), b: Money.new( 4, :USD), c: Money.new( 3, :USD)},
515
- {a: Money.new(-13, :USD), b: Money.new(-4, :USD), c: Money.new(-1, :USD)},
516
- ]
517
- ts.each do |t|
518
- expect(t[:a].modulo(t[:b])).to eq t[:c]
519
- end
520
- end
521
-
522
- it "calculates modulo with Money (different currency)" do
523
- ts = [
524
- {a: Money.new( 13, :USD), b: Money.new( 4, :EUR), c: Money.new( 5, :USD)},
525
- {a: Money.new( 13, :USD), b: Money.new(-4, :EUR), c: Money.new(-3, :USD)},
526
- {a: Money.new(-13, :USD), b: Money.new( 4, :EUR), c: Money.new( 3, :USD)},
527
- {a: Money.new(-13, :USD), b: Money.new(-4, :EUR), c: Money.new(-5, :USD)},
528
- ]
529
- ts.each do |t|
530
- expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
531
- expect(t[:a].modulo(t[:b])).to eq t[:c]
532
- end
533
- end
534
- end
535
-
536
- describe "#%" do
537
- it "calculates modulo with Integer" do
538
- ts = [
539
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 1, :USD)},
540
- {a: Money.new( 13, :USD), b: -4, c: Money.new(-3, :USD)},
541
- {a: Money.new(-13, :USD), b: 4, c: Money.new( 3, :USD)},
542
- {a: Money.new(-13, :USD), b: -4, c: Money.new(-1, :USD)},
543
- ]
544
- ts.each do |t|
545
- expect(t[:a] % t[:b]).to eq t[:c]
546
- end
547
- end
548
-
549
- it "calculates modulo with Money (same currency)" do
550
- ts = [
551
- {a: Money.new( 13, :USD), b: Money.new( 4, :USD), c: Money.new( 1, :USD)},
552
- {a: Money.new( 13, :USD), b: Money.new(-4, :USD), c: Money.new(-3, :USD)},
553
- {a: Money.new(-13, :USD), b: Money.new( 4, :USD), c: Money.new( 3, :USD)},
554
- {a: Money.new(-13, :USD), b: Money.new(-4, :USD), c: Money.new(-1, :USD)},
555
- ]
556
- ts.each do |t|
557
- expect(t[:a] % t[:b]).to eq t[:c]
558
- end
559
- end
560
-
561
- it "calculates modulo with Money (different currency)" do
562
- ts = [
563
- {a: Money.new( 13, :USD), b: Money.new( 4, :EUR), c: Money.new( 5, :USD)},
564
- {a: Money.new( 13, :USD), b: Money.new(-4, :EUR), c: Money.new(-3, :USD)},
565
- {a: Money.new(-13, :USD), b: Money.new( 4, :EUR), c: Money.new( 3, :USD)},
566
- {a: Money.new(-13, :USD), b: Money.new(-4, :EUR), c: Money.new(-5, :USD)},
567
- ]
568
- ts.each do |t|
569
- expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
570
- expect(t[:a] % t[:b]).to eq t[:c]
571
- end
572
- end
573
- end
574
-
575
- describe "#remainder" do
576
- it "calculates remainder with Integer" do
577
- ts = [
578
- {a: Money.new( 13, :USD), b: 4, c: Money.new( 1, :USD)},
579
- {a: Money.new( 13, :USD), b: -4, c: Money.new( 1, :USD)},
580
- {a: Money.new(-13, :USD), b: 4, c: Money.new(-1, :USD)},
581
- {a: Money.new(-13, :USD), b: -4, c: Money.new(-1, :USD)},
582
- ]
583
- ts.each do |t|
584
- expect(t[:a].remainder(t[:b])).to eq t[:c]
585
- end
586
- end
587
-
588
- it_behaves_like 'instance with custom bank', :remainder, -1
589
- end
590
-
591
- describe "#abs" do
592
- it "returns the absolute value as a new Money object" do
593
- n = Money.new(-1, :USD)
594
- expect(n.abs).to eq Money.new( 1, :USD)
595
- expect(n).to eq Money.new(-1, :USD)
596
- end
597
-
598
- it "preserves the class in the result when using a subclass of Money" do
599
- special_money_class = Class.new(Money)
600
- expect(special_money_class.new(-1).abs).to be_a special_money_class
601
- end
602
-
603
- it_behaves_like 'instance with custom bank', :abs
604
- end
605
-
606
- describe "#zero?" do
607
- it "returns whether the amount is 0" do
608
- expect(Money.new(0, "USD")).to be_zero
609
- expect(Money.new(0, "EUR")).to be_zero
610
- expect(Money.new(1, "USD")).not_to be_zero
611
- expect(Money.new(10, "YEN")).not_to be_zero
612
- expect(Money.new(-1, "EUR")).not_to be_zero
613
- end
614
- end
615
-
616
- describe "#nonzero?" do
617
- it "returns whether the amount is not 0" do
618
- expect(Money.new(0, "USD")).not_to be_nonzero
619
- expect(Money.new(0, "EUR")).not_to be_nonzero
620
- expect(Money.new(1, "USD")).to be_nonzero
621
- expect(Money.new(10, "YEN")).to be_nonzero
622
- expect(Money.new(-1, "EUR")).to be_nonzero
623
- end
624
-
625
- it "has the same return-value semantics as Numeric#nonzero?" do
626
- expect(Money.new(0, "USD").nonzero?).to be_nil
627
-
628
- money = Money.new(1, "USD")
629
- expect(money.nonzero?).to be_equal(money)
630
- end
631
- end
632
-
633
- describe "#coerce" do
634
- it 'allows non-default currency money objects to be summed' do
635
- result = 0 + Money.new(4, 'EUR') + Money.new(5, 'EUR')
636
- expect(result).to eq Money.new(9, 'EUR')
637
- end
638
-
639
- it "allows mathematical operations by coercing arguments" do
640
- result = 2 * Money.new(4, 'USD')
641
- expect(result).to eq Money.new(8, 'USD')
642
- end
643
-
644
- it "raises TypeError dividing by a Money (unless other is a Money)" do
645
- expect {
646
- 2 / Money.new(2, 'USD')
647
- }.to raise_exception(TypeError)
648
- end
649
-
650
- it "raises TypeError subtracting by a Money (unless other is a Money)" do
651
- expect {
652
- 2 - Money.new(2, 'USD')
653
- }.to raise_exception(TypeError)
654
- end
655
-
656
- it "raises TypeError adding by a Money (unless other is a Money)" do
657
- expect {
658
- 2 + Money.new(2, 'USD')
659
- }.to raise_exception(TypeError)
660
- end
661
-
662
- it "allows subtraction from numeric zero" do
663
- result = 0 - Money.new(4, 'USD')
664
- expect(result).to eq Money.new(-4, 'USD')
665
- end
666
-
667
- it "allows addition from numeric zero" do
668
- result = 0 + Money.new(4, 'USD')
669
- expect(result).to eq Money.new(4, 'USD')
670
- end
671
-
672
- it "treats multiplication as commutative" do
673
- expect {
674
- 2 * Money.new(2, 'USD')
675
- }.to_not raise_exception
676
- result = 2 * Money.new(2, 'USD')
677
- expect(result).to eq(Money.new(4, 'USD'))
678
- end
679
-
680
- it "doesn't work with non-numerics" do
681
- expect {
682
- "2" * Money.new(2, 'USD')
683
- }.to raise_exception(TypeError)
684
- end
685
-
686
- it "correctly handles <=>" do
687
- expect {
688
- 2 < Money.new(2, 'USD')
689
- }.to raise_exception(ArgumentError)
690
-
691
- expect {
692
- 2 > Money.new(2, 'USD')
693
- }.to raise_exception(ArgumentError)
694
-
695
- expect {
696
- 2 <= Money.new(2, 'USD')
697
- }.to raise_exception(ArgumentError)
698
-
699
- expect {
700
- 2 >= Money.new(2, 'USD')
701
- }.to raise_exception(ArgumentError)
702
-
703
- expect(2 <=> Money.new(2, 'USD')).to be_nil
704
- end
705
-
706
- it 'compares with numeric 0' do
707
- expect(0 < Money.usd(1)).to eq true
708
- expect(0.0 > Money.usd(1)).to eq false
709
- expect(0.0 >= Money.usd(0)).to eq true
710
- end
711
-
712
- it "raises exceptions for all numeric types, not just Integer" do
713
- expect {
714
- 2.0 / Money.new(2, 'USD')
715
- }.to raise_exception(TypeError)
716
-
717
- expect {
718
- Rational(2,3) / Money.new(2, 'USD')
719
- }.to raise_exception(TypeError)
720
-
721
- expect {
722
- BigDecimal(2) / Money.new(2, 'USD')
723
- }.to raise_exception(TypeError)
724
- end
725
- end
726
-
727
- %w(+ - / divmod remainder).each do |op|
728
- describe "##{op}" do
729
- subject { ->(other = self.other) { instance.send(op, other) } }
730
- let(:instance) { Money.usd(1) }
731
-
732
- context 'when conversions disallowed' do
733
- around do |ex|
734
- begin
735
- old = Money.default_bank
736
- Money.disallow_currency_conversion!
737
- ex.run
738
- ensure
739
- Money.default_bank = old
740
- end
741
- end
742
-
743
- context 'and other is money with different currency' do
744
- let(:other) { Money.gbp(1) }
745
- it { should raise_error Money::Bank::DifferentCurrencyError }
746
-
747
- context 'even for zero' do
748
- let(:instance) { Money.usd(0) }
749
- let(:other) { Money.gbp(0) }
750
- it { should raise_error Money::Bank::DifferentCurrencyError }
751
- end
752
- end
753
- end
754
- end
755
- end
756
- end