money 6.5.1 → 6.6.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/.travis.yml +1 -0
- data/AUTHORS +7 -0
- data/CHANGELOG.md +15 -0
- data/README.md +121 -3
- data/Rakefile +0 -1
- data/config/currency_iso.json +8 -8
- data/lib/money/bank/base.rb +0 -2
- data/lib/money/bank/variable_exchange.rb +80 -85
- data/lib/money/currency.rb +50 -35
- data/lib/money/money/arithmetic.rb +9 -9
- data/lib/money/money/formatting.rb +30 -8
- data/lib/money/money.rb +32 -10
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/version.rb +1 -1
- data/money.gemspec +1 -1
- data/spec/bank/base_spec.rb +62 -58
- data/spec/bank/single_currency_spec.rb +10 -6
- data/spec/bank/variable_exchange_spec.rb +205 -223
- data/spec/currency/loader_spec.rb +20 -0
- data/spec/currency_spec.rb +275 -243
- data/spec/money/arithmetic_spec.rb +16 -34
- data/spec/money/formatting_spec.rb +19 -12
- data/spec/money_spec.rb +86 -97
- data/spec/rates_store/memory_spec.rb +71 -0
- data/spec/spec_helper.rb +10 -0
- metadata +10 -5
@@ -2,274 +2,256 @@ require 'spec_helper'
|
|
2
2
|
require 'json'
|
3
3
|
require 'yaml'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
bank
|
5
|
+
class Money
|
6
|
+
module Bank
|
7
|
+
describe VariableExchange do
|
8
|
+
|
9
|
+
describe "#initialize" do
|
10
|
+
context "without &block" do
|
11
|
+
let(:bank) {
|
12
|
+
VariableExchange.new.tap do |bank|
|
13
|
+
bank.add_rate('USD', 'EUR', 1.33)
|
14
|
+
end
|
15
|
+
}
|
16
|
+
|
17
|
+
describe '#store' do
|
18
|
+
it 'defaults to Memory store' do
|
19
|
+
expect(bank.store).to be_a(Money::RatesStore::Memory)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'custom store' do
|
24
|
+
let(:custom_store) { Object.new }
|
25
|
+
|
26
|
+
let(:bank) { VariableExchange.new(custom_store) }
|
27
|
+
|
28
|
+
it 'sets #store to be custom store' do
|
29
|
+
expect(bank.store).to eql(custom_store)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#exchange_with" do
|
34
|
+
it "accepts str" do
|
35
|
+
expect { bank.exchange_with(Money.new(100, 'USD'), 'EUR') }.to_not raise_exception
|
36
|
+
end
|
37
|
+
|
38
|
+
it "accepts currency" do
|
39
|
+
expect { bank.exchange_with(Money.new(100, 'USD'), Currency.wrap('EUR')) }.to_not raise_exception
|
40
|
+
end
|
41
|
+
|
42
|
+
it "exchanges one currency to another" do
|
43
|
+
expect(bank.exchange_with(Money.new(100, 'USD'), 'EUR')).to eq Money.new(133, 'EUR')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "truncates extra digits" do
|
47
|
+
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR')).to eq Money.new(13, 'EUR')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "raises an UnknownCurrency exception when an unknown currency is requested" do
|
51
|
+
expect { bank.exchange_with(Money.new(100, 'USD'), 'BBB') }.to raise_exception(Currency::UnknownCurrency)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises an UnknownRate exception when an unknown rate is requested" do
|
55
|
+
expect { bank.exchange_with(Money.new(100, 'USD'), 'JPY') }.to raise_exception(UnknownRate)
|
56
|
+
end
|
57
|
+
|
58
|
+
#it "rounds the exchanged result down" do
|
59
|
+
# bank.add_rate("USD", "EUR", 0.788332676)
|
60
|
+
# bank.add_rate("EUR", "YEN", 122.631477)
|
61
|
+
# expect(bank.exchange_with(Money.new(10_00, "USD"), "EUR")).to eq Money.new(788, "EUR")
|
62
|
+
# expect(bank.exchange_with(Money.new(500_00, "EUR"), "YEN")).to eq Money.new(6131573, "YEN")
|
63
|
+
#end
|
64
|
+
|
65
|
+
it "accepts a custom truncation method" do
|
66
|
+
proc = Proc.new { |n| n.ceil }
|
67
|
+
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc)).to eq Money.new(14, 'EUR')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'works with big numbers' do
|
71
|
+
amount = 10**20
|
72
|
+
expect(bank.exchange_with(Money.usd(amount), :EUR)).to eq Money.eur(1.33 * amount)
|
73
|
+
end
|
74
|
+
end
|
12
75
|
end
|
13
|
-
}
|
14
76
|
|
15
|
-
|
16
|
-
|
17
|
-
|
77
|
+
context "with &block" do
|
78
|
+
let(:bank) {
|
79
|
+
proc = Proc.new { |n| n.ceil }
|
80
|
+
VariableExchange.new(&proc).tap do |bank|
|
81
|
+
bank.add_rate('USD', 'EUR', 1.33)
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
describe "#exchange_with" do
|
86
|
+
it "uses the stored truncation method" do
|
87
|
+
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR')).to eq Money.new(14, 'EUR')
|
88
|
+
end
|
89
|
+
|
90
|
+
it "accepts a custom truncation method" do
|
91
|
+
proc = Proc.new { |n| n.ceil + 1 }
|
92
|
+
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc)).to eq Money.new(15, 'EUR')
|
93
|
+
end
|
94
|
+
end
|
18
95
|
end
|
96
|
+
end
|
19
97
|
|
20
|
-
|
21
|
-
|
98
|
+
describe "#add_rate" do
|
99
|
+
it 'delegates to store#add_rate' do
|
100
|
+
expect(subject.store).to receive(:add_rate).with('USD', 'EUR', 1.25).and_return 1.25
|
101
|
+
expect(subject.add_rate('USD', 'EUR', 1.25)).to eql 1.25
|
22
102
|
end
|
23
103
|
|
24
|
-
it "
|
25
|
-
expect(
|
26
|
-
|
104
|
+
it "adds rates with correct ISO codes" do
|
105
|
+
expect(subject.store).to receive(:add_rate).with('USD', 'EUR', 0.788332676)
|
106
|
+
subject.add_rate("USD", "EUR", 0.788332676)
|
27
107
|
|
28
|
-
|
29
|
-
|
108
|
+
expect(subject.store).to receive(:add_rate).with('EUR', 'JPY', 122.631477)
|
109
|
+
subject.add_rate("EUR", "YEN", 122.631477)
|
30
110
|
end
|
31
111
|
|
32
|
-
it "
|
33
|
-
|
112
|
+
it "treats currency names case-insensitively" do
|
113
|
+
subject.add_rate("usd", "eur", 1)
|
114
|
+
expect(subject.get_rate('USD', 'EUR')).to eq 1
|
34
115
|
end
|
116
|
+
end
|
35
117
|
|
36
|
-
|
37
|
-
|
118
|
+
describe "#set_rate" do
|
119
|
+
it 'delegates to store#add_rate' do
|
120
|
+
expect(subject.store).to receive(:add_rate).with('USD', 'EUR', 1.25).and_return 1.25
|
121
|
+
expect(subject.set_rate('USD', 'EUR', 1.25)).to eql 1.25
|
38
122
|
end
|
39
123
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# expect(bank.exchange_with(Money.new(500_00, "EUR"), "YEN")).to eq Money.new(6131573, "YEN")
|
45
|
-
#end
|
124
|
+
it "sets a rate" do
|
125
|
+
subject.set_rate('USD', 'EUR', 1.25)
|
126
|
+
expect(subject.store.get_rate('USD', 'EUR')).to eq 1.25
|
127
|
+
end
|
46
128
|
|
47
|
-
it "
|
48
|
-
|
49
|
-
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc)).to eq Money.new(14, 'EUR')
|
129
|
+
it "raises an UnknownCurrency exception when an unknown currency is passed" do
|
130
|
+
expect { subject.set_rate('AAA', 'BBB', 1.25) }.to raise_exception(Currency::UnknownCurrency)
|
50
131
|
end
|
51
132
|
end
|
52
|
-
end
|
53
133
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
bank.add_rate('USD', 'EUR', 1.33)
|
134
|
+
describe "#get_rate" do
|
135
|
+
it "returns a rate" do
|
136
|
+
subject.set_rate('USD', 'EUR', 1.25)
|
137
|
+
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
59
138
|
end
|
60
|
-
}
|
61
139
|
|
62
|
-
|
63
|
-
|
64
|
-
expect(bank.exchange_with(Money.new(10, 'USD'), 'EUR')).to eq Money.new(14, 'EUR')
|
140
|
+
it "raises an UnknownCurrency exception when an unknown currency is passed" do
|
141
|
+
expect { subject.get_rate('AAA', 'BBB') }.to raise_exception(Currency::UnknownCurrency)
|
65
142
|
end
|
66
143
|
|
67
|
-
it "
|
68
|
-
|
69
|
-
|
144
|
+
it "delegates options to store, options are a no-op" do
|
145
|
+
expect(subject.store).to receive(:get_rate).with('USD', 'EUR')
|
146
|
+
subject.get_rate('USD', 'EUR', :without_mutex => true)
|
70
147
|
end
|
71
148
|
end
|
72
|
-
end
|
73
|
-
end
|
74
149
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
150
|
+
describe "#export_rates" do
|
151
|
+
before :each do
|
152
|
+
subject.set_rate('USD', 'EUR', 1.25)
|
153
|
+
subject.set_rate('USD', 'JPY', 2.55)
|
79
154
|
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
it "treats currency names case-insensitively" do
|
85
|
-
subject.add_rate("usd", "eur", 1)
|
86
|
-
expect(subject.instance_variable_get(:@rates)['USD_TO_EUR']).to eq 1
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
describe "#set_rate" do
|
91
|
-
it "sets a rate" do
|
92
|
-
subject.set_rate('USD', 'EUR', 1.25)
|
93
|
-
expect(subject.instance_variable_get(:@rates)['USD_TO_EUR']).to eq 1.25
|
94
|
-
end
|
95
|
-
|
96
|
-
it "raises an UnknownCurrency exception when an unknown currency is passed" do
|
97
|
-
expect { subject.set_rate('AAA', 'BBB', 1.25) }.to raise_exception(Money::Currency::UnknownCurrency)
|
98
|
-
end
|
99
|
-
|
100
|
-
it "uses a mutex by default" do
|
101
|
-
expect(subject.instance_variable_get(:@mutex)).to receive(:synchronize)
|
102
|
-
subject.set_rate('USD', 'EUR', 1.25)
|
103
|
-
end
|
104
|
-
|
105
|
-
it "doesn't use mutex if requested not to" do
|
106
|
-
expect(subject.instance_variable_get(:@mutex)).not_to receive(:synchronize)
|
107
|
-
subject.set_rate('USD', 'EUR', 1.25, :without_mutex => true)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
describe "#get_rate" do
|
112
|
-
it "returns a rate" do
|
113
|
-
subject.set_rate('USD', 'EUR', 1.25)
|
114
|
-
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
115
|
-
end
|
155
|
+
@rates = { "USD_TO_EUR" => 1.25, "USD_TO_JPY" => 2.55 }
|
156
|
+
end
|
116
157
|
|
117
|
-
|
118
|
-
|
119
|
-
|
158
|
+
context "with format == :json" do
|
159
|
+
it "should return rates formatted as json" do
|
160
|
+
json = subject.export_rates(:json)
|
161
|
+
expect(JSON.load(json)).to eq @rates
|
162
|
+
end
|
163
|
+
end
|
120
164
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
165
|
+
context "with format == :ruby" do
|
166
|
+
it "should return rates formatted as ruby objects" do
|
167
|
+
expect(Marshal.load(subject.export_rates(:ruby))).to eq @rates
|
168
|
+
end
|
169
|
+
end
|
125
170
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
171
|
+
context "with format == :yaml" do
|
172
|
+
it "should return rates formatted as yaml" do
|
173
|
+
yaml = subject.export_rates(:yaml)
|
174
|
+
expect(YAML.load(yaml)).to eq @rates
|
175
|
+
end
|
176
|
+
end
|
131
177
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
178
|
+
context "with unknown format" do
|
179
|
+
it "raises Money::Bank::UnknownRateFormat" do
|
180
|
+
expect { subject.export_rates(:foo)}.to raise_error UnknownRateFormat
|
181
|
+
end
|
182
|
+
end
|
136
183
|
|
137
|
-
|
138
|
-
|
184
|
+
context "with :file provided" do
|
185
|
+
it "writes rates to file" do
|
186
|
+
f = double('IO')
|
187
|
+
expect(File).to receive(:open).with('null', 'w').and_yield(f)
|
188
|
+
expect(f).to receive(:write).with(JSON.dump(@rates))
|
139
189
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
expect(JSON.load(json)).to eq @rates
|
144
|
-
end
|
145
|
-
end
|
190
|
+
subject.export_rates(:json, 'null')
|
191
|
+
end
|
192
|
+
end
|
146
193
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
194
|
+
it "delegates execution to store, options are a no-op" do
|
195
|
+
expect(subject.store).to receive(:transaction)
|
196
|
+
subject.export_rates(:yaml, nil, :foo => 1)
|
197
|
+
end
|
152
198
|
|
153
|
-
context "with format == :yaml" do
|
154
|
-
it "should return rates formatted as yaml" do
|
155
|
-
yaml = subject.export_rates(:yaml)
|
156
|
-
expect(YAML.load(yaml)).to eq @rates
|
157
199
|
end
|
158
|
-
end
|
159
200
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
201
|
+
describe "#import_rates" do
|
202
|
+
context "with format == :json" do
|
203
|
+
it "loads the rates provided" do
|
204
|
+
s = '{"USD_TO_EUR":1.25,"USD_TO_JPY":2.55}'
|
205
|
+
subject.import_rates(:json, s)
|
206
|
+
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
207
|
+
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
208
|
+
end
|
209
|
+
end
|
165
210
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
211
|
+
context "with format == :ruby" do
|
212
|
+
it "loads the rates provided" do
|
213
|
+
s = Marshal.dump({"USD_TO_EUR"=>1.25,"USD_TO_JPY"=>2.55})
|
214
|
+
subject.import_rates(:ruby, s)
|
215
|
+
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
216
|
+
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
217
|
+
end
|
218
|
+
end
|
171
219
|
|
172
|
-
|
173
|
-
|
174
|
-
|
220
|
+
context "with format == :yaml" do
|
221
|
+
it "loads the rates provided" do
|
222
|
+
s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
|
223
|
+
subject.import_rates(:yaml, s)
|
224
|
+
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
225
|
+
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
226
|
+
end
|
227
|
+
end
|
175
228
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
229
|
+
context "with unknown format" do
|
230
|
+
it "raises Money::Bank::UnknownRateFormat" do
|
231
|
+
expect { subject.import_rates(:foo, "")}.to raise_error UnknownRateFormat
|
232
|
+
end
|
233
|
+
end
|
180
234
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
235
|
+
it "delegates execution to store#transaction" do
|
236
|
+
expect(subject.store).to receive(:transaction)
|
237
|
+
s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
|
238
|
+
subject.import_rates(:yaml, s, :foo => 1)
|
239
|
+
end
|
186
240
|
|
187
|
-
describe "#import_rates" do
|
188
|
-
context "with format == :json" do
|
189
|
-
it "loads the rates provided" do
|
190
|
-
s = '{"USD_TO_EUR":1.25,"USD_TO_JPY":2.55}'
|
191
|
-
subject.import_rates(:json, s)
|
192
|
-
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
193
|
-
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
194
241
|
end
|
195
|
-
end
|
196
242
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
202
|
-
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
203
|
-
end
|
204
|
-
end
|
243
|
+
describe "#marshal_dump" do
|
244
|
+
it "does not raise an error" do
|
245
|
+
expect { Marshal.dump(subject) }.to_not raise_error
|
246
|
+
end
|
205
247
|
|
206
|
-
|
207
|
-
|
208
|
-
s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
|
209
|
-
subject.import_rates(:yaml, s)
|
210
|
-
expect(subject.get_rate('USD', 'EUR')).to eq 1.25
|
211
|
-
expect(subject.get_rate('USD', 'JPY')).to eq 2.55
|
212
|
-
end
|
213
|
-
end
|
248
|
+
it "works with Marshal.load" do
|
249
|
+
bank = Marshal.load(Marshal.dump(subject))
|
214
250
|
|
215
|
-
|
216
|
-
|
217
|
-
|
251
|
+
expect(bank.rates).to eq subject.rates
|
252
|
+
expect(bank.rounding_method).to eq subject.rounding_method
|
253
|
+
end
|
218
254
|
end
|
219
255
|
end
|
220
|
-
|
221
|
-
it "uses a mutex by default" do
|
222
|
-
expect(subject.instance_variable_get(:@mutex)).to receive(:synchronize)
|
223
|
-
s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
|
224
|
-
subject.import_rates(:yaml, s)
|
225
|
-
end
|
226
|
-
|
227
|
-
it "doesn't use mutex if requested not to" do
|
228
|
-
expect(subject.instance_variable_get(:@mutex)).not_to receive(:synchronize)
|
229
|
-
s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
|
230
|
-
subject.import_rates(:yaml, s, :without_mutex => true)
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
describe "#rate_key_for" do
|
235
|
-
it "accepts str/str" do
|
236
|
-
expect { subject.send(:rate_key_for, 'USD', 'EUR')}.to_not raise_exception
|
237
|
-
end
|
238
|
-
|
239
|
-
it "accepts currency/str" do
|
240
|
-
expect { subject.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR')}.to_not raise_exception
|
241
|
-
end
|
242
|
-
|
243
|
-
it "accepts str/currency" do
|
244
|
-
expect { subject.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR'))}.to_not raise_exception
|
245
|
-
end
|
246
|
-
|
247
|
-
it "accepts currency/currency" do
|
248
|
-
expect { subject.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.to_not raise_exception
|
249
|
-
end
|
250
|
-
|
251
|
-
it "returns a hashkey based on the passed arguments" do
|
252
|
-
expect(subject.send(:rate_key_for, 'USD', 'EUR')).to eq 'USD_TO_EUR'
|
253
|
-
expect(subject.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR')).to eq 'USD_TO_EUR'
|
254
|
-
expect(subject.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR'))).to eq 'USD_TO_EUR'
|
255
|
-
expect(subject.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))).to eq 'USD_TO_EUR'
|
256
|
-
end
|
257
|
-
|
258
|
-
it "raises a Money::Currency::UnknownCurrency exception when an unknown currency is passed" do
|
259
|
-
expect { subject.send(:rate_key_for, 'AAA', 'BBB')}.to raise_exception(Money::Currency::UnknownCurrency)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
describe "#marshal_dump" do
|
264
|
-
it "does not raise an error" do
|
265
|
-
expect { Marshal.dump(subject) }.to_not raise_error
|
266
|
-
end
|
267
|
-
|
268
|
-
it "works with Marshal.load" do
|
269
|
-
bank = Marshal.load(Marshal.dump(subject))
|
270
|
-
|
271
|
-
expect(bank.rates).to eq subject.rates
|
272
|
-
expect(bank.rounding_method).to eq subject.rounding_method
|
273
|
-
end
|
274
256
|
end
|
275
257
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Money::Currency::Loader do
|
5
|
+
class CurrencyLoader
|
6
|
+
include Money::Currency::Loader
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:loader) { CurrencyLoader.new }
|
10
|
+
|
11
|
+
it "returns a currency table hash" do
|
12
|
+
expect(loader.load_currencies).to be_a Hash
|
13
|
+
end
|
14
|
+
|
15
|
+
it "parse currency_iso.json & currency_non_iso.json & currency_backwards_compatible.json" do
|
16
|
+
expect(loader).to receive(:parse_currency_file).exactly(3).times.and_return({})
|
17
|
+
|
18
|
+
loader.load_currencies
|
19
|
+
end
|
20
|
+
end
|