money 6.7.0 → 6.13.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 +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
|