money 6.13.0 → 6.13.8

Sign up to get free protection for your applications and to get access to all the features.
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