money 6.7.0 → 6.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/.travis.yml +22 -5
- data/AUTHORS +5 -0
- data/CHANGELOG.md +109 -3
- data/Gemfile +13 -4
- data/LICENSE +2 -0
- data/README.md +69 -49
- data/config/currency_backwards_compatible.json +30 -0
- data/config/currency_iso.json +139 -62
- data/config/currency_non_iso.json +66 -2
- data/lib/money.rb +0 -13
- data/lib/money/bank/variable_exchange.rb +9 -22
- data/lib/money/currency.rb +35 -38
- data/lib/money/currency/heuristics.rb +1 -144
- data/lib/money/currency/loader.rb +1 -1
- data/lib/money/locale_backend/base.rb +7 -0
- data/lib/money/locale_backend/errors.rb +6 -0
- data/lib/money/locale_backend/i18n.rb +24 -0
- data/lib/money/locale_backend/legacy.rb +28 -0
- data/lib/money/money.rb +120 -151
- data/lib/money/money/allocation.rb +37 -0
- data/lib/money/money/arithmetic.rb +57 -52
- data/lib/money/money/constructors.rb +1 -2
- data/lib/money/money/formatter.rb +397 -0
- data/lib/money/money/formatting_rules.rb +120 -0
- data/lib/money/money/locale_backend.rb +20 -0
- data/lib/money/rates_store/memory.rb +1 -2
- data/lib/money/version.rb +1 -1
- data/money.gemspec +10 -16
- data/spec/bank/variable_exchange_spec.rb +7 -3
- data/spec/currency/heuristics_spec.rb +2 -153
- data/spec/currency_spec.rb +45 -4
- data/spec/locale_backend/i18n_spec.rb +62 -0
- data/spec/locale_backend/legacy_spec.rb +74 -0
- data/spec/money/allocation_spec.rb +130 -0
- data/spec/money/arithmetic_spec.rb +217 -104
- data/spec/money/constructors_spec.rb +0 -12
- data/spec/money/formatting_spec.rb +320 -179
- data/spec/money/locale_backend_spec.rb +14 -0
- data/spec/money_spec.rb +159 -26
- data/spec/rates_store/memory_spec.rb +13 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/money_examples.rb +14 -0
- metadata +32 -41
- data/lib/money/money/formatting.rb +0 -417
@@ -0,0 +1,62 @@
|
|
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
|
@@ -0,0 +1,74 @@
|
|
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
|
@@ -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
|
@@ -12,6 +12,8 @@ describe Money do
|
|
12
12
|
special_money_class = Class.new(Money)
|
13
13
|
expect(- special_money_class.new(10_00)).to be_a special_money_class
|
14
14
|
end
|
15
|
+
|
16
|
+
it_behaves_like 'instance with custom bank', :-@
|
15
17
|
end
|
16
18
|
|
17
19
|
describe "#==" do
|
@@ -45,6 +47,19 @@ describe Money do
|
|
45
47
|
expect(Money.new(2_50, "USD")).not_to eq klass.new(3_00, "USD")
|
46
48
|
expect(Money.new(1_00, "GBP")).not_to eq klass.new(1_00, "USD")
|
47
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
|
48
63
|
end
|
49
64
|
|
50
65
|
describe "#eql?" do
|
@@ -55,6 +70,12 @@ describe Money do
|
|
55
70
|
expect(Money.new(1_00, "USD").eql?(Money.new(99_00, "EUR"))).to be false
|
56
71
|
end
|
57
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
|
+
|
58
79
|
it "returns false if used to compare with an object that doesn't inherit from Money" do
|
59
80
|
expect(Money.new(1_00, "USD").eql?(Object.new)).to be false
|
60
81
|
expect(Money.new(1_00, "USD").eql?(Class)).to be false
|
@@ -108,17 +129,45 @@ describe Money do
|
|
108
129
|
expect(Money.new(1_00) <=> klass.new(2_00)).to be < 0
|
109
130
|
end
|
110
131
|
|
111
|
-
it "
|
132
|
+
it "returns nill when comparing with an object that doesn't inherit from Money" do
|
112
133
|
expect(Money.new(1_00) <=> 100).to be_nil
|
113
|
-
|
114
134
|
expect(Money.new(1_00) <=> Object.new).to be_nil
|
115
|
-
|
116
135
|
expect(Money.new(1_00) <=> Class).to be_nil
|
117
|
-
|
118
136
|
expect(Money.new(1_00) <=> Kernel).to be_nil
|
119
|
-
|
120
137
|
expect(Money.new(1_00) <=> /foo/).to be_nil
|
121
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
|
122
171
|
end
|
123
172
|
|
124
173
|
describe "#positive?" do
|
@@ -160,7 +209,7 @@ describe Money do
|
|
160
209
|
expect(Money.new(10_00, "USD") + other).to eq Money.new(19_00, "USD")
|
161
210
|
end
|
162
211
|
|
163
|
-
it "adds
|
212
|
+
it "adds Integer 0 to money and returns the same ammount" do
|
164
213
|
expect(Money.new(10_00) + 0).to eq Money.new(10_00)
|
165
214
|
end
|
166
215
|
|
@@ -168,6 +217,8 @@ describe Money do
|
|
168
217
|
special_money_class = Class.new(Money)
|
169
218
|
expect(special_money_class.new(10_00, "USD") + Money.new(90, "USD")).to be_a special_money_class
|
170
219
|
end
|
220
|
+
|
221
|
+
it_behaves_like 'instance with custom bank', :+, Money.new(1)
|
171
222
|
end
|
172
223
|
|
173
224
|
describe "#-" do
|
@@ -181,7 +232,7 @@ describe Money do
|
|
181
232
|
expect(Money.new(10_00, "USD") - other).to eq Money.new(1_00, "USD")
|
182
233
|
end
|
183
234
|
|
184
|
-
it "subtract
|
235
|
+
it "subtract Integer 0 to money and returns the same ammount" do
|
185
236
|
expect(Money.new(10_00) - 0).to eq Money.new(10_00)
|
186
237
|
end
|
187
238
|
|
@@ -189,15 +240,17 @@ describe Money do
|
|
189
240
|
special_money_class = Class.new(Money)
|
190
241
|
expect(special_money_class.new(10_00, "USD") - Money.new(90, "USD")).to be_a special_money_class
|
191
242
|
end
|
243
|
+
|
244
|
+
it_behaves_like 'instance with custom bank', :-, Money.new(1)
|
192
245
|
end
|
193
246
|
|
194
247
|
describe "#*" do
|
195
|
-
it "multiplies Money by
|
248
|
+
it "multiplies Money by Integer and returns Money" do
|
196
249
|
ts = [
|
197
|
-
{:
|
198
|
-
{:
|
199
|
-
{:
|
200
|
-
{:
|
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)},
|
201
254
|
]
|
202
255
|
ts.each do |t|
|
203
256
|
expect(t[:a] * t[:b]).to eq t[:c]
|
@@ -205,30 +258,32 @@ describe Money do
|
|
205
258
|
end
|
206
259
|
|
207
260
|
it "does not multiply Money by Money (same currency)" do
|
208
|
-
expect { Money.new(
|
261
|
+
expect { Money.new(10, :USD) * Money.new(4, :USD) }.to raise_error(TypeError)
|
209
262
|
end
|
210
263
|
|
211
264
|
it "does not multiply Money by Money (different currency)" do
|
212
|
-
expect { Money.new(
|
265
|
+
expect { Money.new(10, :USD) * Money.new(4, :EUR) }.to raise_error(TypeError)
|
213
266
|
end
|
214
267
|
|
215
268
|
it "does not multiply Money by an object which is NOT a number" do
|
216
|
-
expect { Money.new(
|
269
|
+
expect { Money.new(10, :USD) * 'abc' }.to raise_error(TypeError)
|
217
270
|
end
|
218
271
|
|
219
272
|
it "preserves the class in the result when using a subclass of Money" do
|
220
273
|
special_money_class = Class.new(Money)
|
221
274
|
expect(special_money_class.new(10_00, "USD") * 2).to be_a special_money_class
|
222
275
|
end
|
276
|
+
|
277
|
+
it_behaves_like 'instance with custom bank', :*, 1
|
223
278
|
end
|
224
279
|
|
225
280
|
describe "#/" do
|
226
|
-
it "divides Money by
|
281
|
+
it "divides Money by Integer and returns Money" do
|
227
282
|
ts = [
|
228
|
-
{:
|
229
|
-
{:
|
230
|
-
{:
|
231
|
-
{:
|
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)},
|
232
287
|
]
|
233
288
|
ts.each do |t|
|
234
289
|
expect(t[:a] / t[:b]).to eq t[:c]
|
@@ -280,10 +335,10 @@ describe Money do
|
|
280
335
|
|
281
336
|
it "divides Money by Money (same currency) and returns Float" do
|
282
337
|
ts = [
|
283
|
-
{:
|
284
|
-
{:
|
285
|
-
{:
|
286
|
-
{:
|
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},
|
287
342
|
]
|
288
343
|
ts.each do |t|
|
289
344
|
expect(t[:a] / t[:b]).to eq t[:c]
|
@@ -292,10 +347,10 @@ describe Money do
|
|
292
347
|
|
293
348
|
it "divides Money by Money (different currency) and returns Float" do
|
294
349
|
ts = [
|
295
|
-
{:
|
296
|
-
{:
|
297
|
-
{:
|
298
|
-
{:
|
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},
|
299
354
|
]
|
300
355
|
ts.each do |t|
|
301
356
|
expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
|
@@ -306,25 +361,27 @@ describe Money do
|
|
306
361
|
context "with infinite_precision", :infinite_precision do
|
307
362
|
it "uses BigDecimal division" do
|
308
363
|
ts = [
|
309
|
-
{:
|
310
|
-
{:
|
311
|
-
{:
|
312
|
-
{:
|
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)},
|
313
368
|
]
|
314
369
|
ts.each do |t|
|
315
370
|
expect(t[:a] / t[:b]).to eq t[:c]
|
316
371
|
end
|
317
372
|
end
|
318
373
|
end
|
374
|
+
|
375
|
+
it_behaves_like 'instance with custom bank', :/, 1
|
319
376
|
end
|
320
377
|
|
321
378
|
describe "#div" do
|
322
|
-
it "divides Money by
|
379
|
+
it "divides Money by Integer and returns Money" do
|
323
380
|
ts = [
|
324
|
-
{:
|
325
|
-
{:
|
326
|
-
{:
|
327
|
-
{:
|
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)},
|
328
385
|
]
|
329
386
|
ts.each do |t|
|
330
387
|
expect(t[:a].div(t[:b])).to eq t[:c]
|
@@ -333,10 +390,10 @@ describe Money do
|
|
333
390
|
|
334
391
|
it "divides Money by Money (same currency) and returns Float" do
|
335
392
|
ts = [
|
336
|
-
{:
|
337
|
-
{:
|
338
|
-
{:
|
339
|
-
{:
|
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},
|
340
397
|
]
|
341
398
|
ts.each do |t|
|
342
399
|
expect(t[:a].div(t[:b])).to eq t[:c]
|
@@ -345,10 +402,10 @@ describe Money do
|
|
345
402
|
|
346
403
|
it "divides Money by Money (different currency) and returns Float" do
|
347
404
|
ts = [
|
348
|
-
{:
|
349
|
-
{:
|
350
|
-
{:
|
351
|
-
{:
|
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},
|
352
409
|
]
|
353
410
|
ts.each do |t|
|
354
411
|
expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
|
@@ -359,10 +416,10 @@ describe Money do
|
|
359
416
|
context "with infinite_precision", :infinite_precision do
|
360
417
|
it "uses BigDecimal division" do
|
361
418
|
ts = [
|
362
|
-
{:
|
363
|
-
{:
|
364
|
-
{:
|
365
|
-
{:
|
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)},
|
366
423
|
]
|
367
424
|
ts.each do |t|
|
368
425
|
expect(t[:a].div(t[:b])).to eq t[:c]
|
@@ -372,12 +429,12 @@ describe Money do
|
|
372
429
|
end
|
373
430
|
|
374
431
|
describe "#divmod" do
|
375
|
-
it "calculates division and modulo with
|
432
|
+
it "calculates division and modulo with Integer" do
|
376
433
|
ts = [
|
377
|
-
{:
|
378
|
-
{:
|
379
|
-
{:
|
380
|
-
{:
|
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)]},
|
381
438
|
]
|
382
439
|
ts.each do |t|
|
383
440
|
expect(t[:a].divmod(t[:b])).to eq t[:c]
|
@@ -386,10 +443,10 @@ describe Money do
|
|
386
443
|
|
387
444
|
it "calculates division and modulo with Money (same currency)" do
|
388
445
|
ts = [
|
389
|
-
{:
|
390
|
-
{:
|
391
|
-
{:
|
392
|
-
{:
|
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)]},
|
393
450
|
]
|
394
451
|
ts.each do |t|
|
395
452
|
expect(t[:a].divmod(t[:b])).to eq t[:c]
|
@@ -398,10 +455,10 @@ describe Money do
|
|
398
455
|
|
399
456
|
it "calculates division and modulo with Money (different currency)" do
|
400
457
|
ts = [
|
401
|
-
{:
|
402
|
-
{:
|
403
|
-
{:
|
404
|
-
{:
|
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)]},
|
405
462
|
]
|
406
463
|
ts.each do |t|
|
407
464
|
expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
|
@@ -412,10 +469,10 @@ describe Money do
|
|
412
469
|
context "with infinite_precision", :infinite_precision do
|
413
470
|
it "uses BigDecimal division" do
|
414
471
|
ts = [
|
415
|
-
{:
|
416
|
-
{:
|
417
|
-
{:
|
418
|
-
{:
|
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)]},
|
419
476
|
]
|
420
477
|
ts.each do |t|
|
421
478
|
expect(t[:a].divmod(t[:b])).to eq t[:c]
|
@@ -432,15 +489,18 @@ describe Money do
|
|
432
489
|
special_money_class = Class.new(Money)
|
433
490
|
expect(special_money_class.new(10_00, "USD").divmod(special_money_class.new(4_00)).last).to be_a special_money_class
|
434
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
|
435
495
|
end
|
436
496
|
|
437
497
|
describe "#modulo" do
|
438
|
-
it "calculates modulo with
|
498
|
+
it "calculates modulo with Integer" do
|
439
499
|
ts = [
|
440
|
-
{:
|
441
|
-
{:
|
442
|
-
{:
|
443
|
-
{:
|
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)},
|
444
504
|
]
|
445
505
|
ts.each do |t|
|
446
506
|
expect(t[:a].modulo(t[:b])).to eq t[:c]
|
@@ -449,10 +509,10 @@ describe Money do
|
|
449
509
|
|
450
510
|
it "calculates modulo with Money (same currency)" do
|
451
511
|
ts = [
|
452
|
-
{:
|
453
|
-
{:
|
454
|
-
{:
|
455
|
-
{:
|
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)},
|
456
516
|
]
|
457
517
|
ts.each do |t|
|
458
518
|
expect(t[:a].modulo(t[:b])).to eq t[:c]
|
@@ -461,10 +521,10 @@ describe Money do
|
|
461
521
|
|
462
522
|
it "calculates modulo with Money (different currency)" do
|
463
523
|
ts = [
|
464
|
-
{:
|
465
|
-
{:
|
466
|
-
{:
|
467
|
-
{:
|
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)},
|
468
528
|
]
|
469
529
|
ts.each do |t|
|
470
530
|
expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
|
@@ -474,12 +534,12 @@ describe Money do
|
|
474
534
|
end
|
475
535
|
|
476
536
|
describe "#%" do
|
477
|
-
it "calculates modulo with
|
537
|
+
it "calculates modulo with Integer" do
|
478
538
|
ts = [
|
479
|
-
{:
|
480
|
-
{:
|
481
|
-
{:
|
482
|
-
{:
|
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)},
|
483
543
|
]
|
484
544
|
ts.each do |t|
|
485
545
|
expect(t[:a] % t[:b]).to eq t[:c]
|
@@ -488,10 +548,10 @@ describe Money do
|
|
488
548
|
|
489
549
|
it "calculates modulo with Money (same currency)" do
|
490
550
|
ts = [
|
491
|
-
{:
|
492
|
-
{:
|
493
|
-
{:
|
494
|
-
{:
|
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)},
|
495
555
|
]
|
496
556
|
ts.each do |t|
|
497
557
|
expect(t[:a] % t[:b]).to eq t[:c]
|
@@ -500,10 +560,10 @@ describe Money do
|
|
500
560
|
|
501
561
|
it "calculates modulo with Money (different currency)" do
|
502
562
|
ts = [
|
503
|
-
{:
|
504
|
-
{:
|
505
|
-
{:
|
506
|
-
{:
|
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)},
|
507
567
|
]
|
508
568
|
ts.each do |t|
|
509
569
|
expect(t[:b]).to receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
|
@@ -513,17 +573,19 @@ describe Money do
|
|
513
573
|
end
|
514
574
|
|
515
575
|
describe "#remainder" do
|
516
|
-
it "calculates remainder with
|
576
|
+
it "calculates remainder with Integer" do
|
517
577
|
ts = [
|
518
|
-
{:
|
519
|
-
{:
|
520
|
-
{:
|
521
|
-
{:
|
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)},
|
522
582
|
]
|
523
583
|
ts.each do |t|
|
524
584
|
expect(t[:a].remainder(t[:b])).to eq t[:c]
|
525
585
|
end
|
526
586
|
end
|
587
|
+
|
588
|
+
it_behaves_like 'instance with custom bank', :remainder, -1
|
527
589
|
end
|
528
590
|
|
529
591
|
describe "#abs" do
|
@@ -537,6 +599,8 @@ describe Money do
|
|
537
599
|
special_money_class = Class.new(Money)
|
538
600
|
expect(special_money_class.new(-1).abs).to be_a special_money_class
|
539
601
|
end
|
602
|
+
|
603
|
+
it_behaves_like 'instance with custom bank', :abs
|
540
604
|
end
|
541
605
|
|
542
606
|
describe "#zero?" do
|
@@ -567,6 +631,11 @@ describe Money do
|
|
567
631
|
end
|
568
632
|
|
569
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
|
+
|
570
639
|
it "allows mathematical operations by coercing arguments" do
|
571
640
|
result = 2 * Money.new(4, 'USD')
|
572
641
|
expect(result).to eq Money.new(8, 'USD')
|
@@ -590,6 +659,16 @@ describe Money do
|
|
590
659
|
}.to raise_exception(TypeError)
|
591
660
|
end
|
592
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
|
+
|
593
672
|
it "treats multiplication as commutative" do
|
594
673
|
expect {
|
595
674
|
2 * Money.new(2, 'USD')
|
@@ -607,23 +686,27 @@ describe Money do
|
|
607
686
|
it "correctly handles <=>" do
|
608
687
|
expect {
|
609
688
|
2 < Money.new(2, 'USD')
|
610
|
-
}.to raise_exception(
|
689
|
+
}.to raise_exception(ArgumentError)
|
611
690
|
|
612
691
|
expect {
|
613
692
|
2 > Money.new(2, 'USD')
|
614
|
-
}.to raise_exception(
|
693
|
+
}.to raise_exception(ArgumentError)
|
615
694
|
|
616
695
|
expect {
|
617
696
|
2 <= Money.new(2, 'USD')
|
618
|
-
}.to raise_exception(
|
697
|
+
}.to raise_exception(ArgumentError)
|
619
698
|
|
620
699
|
expect {
|
621
700
|
2 >= Money.new(2, 'USD')
|
622
|
-
}.to raise_exception(
|
701
|
+
}.to raise_exception(ArgumentError)
|
623
702
|
|
624
|
-
expect
|
625
|
-
|
626
|
-
|
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
|
627
710
|
end
|
628
711
|
|
629
712
|
it "raises exceptions for all numeric types, not just Integer" do
|
@@ -640,4 +723,34 @@ describe Money do
|
|
640
723
|
}.to raise_exception(TypeError)
|
641
724
|
end
|
642
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
|
643
756
|
end
|