money-open-exchange-rates 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.markdown +5 -0
- data/lib/money/bank/open_exchange_rates_bank.rb +21 -20
- data/test/open_exchange_rates_bank_test.rb +108 -87
- data/test/test_helper.rb +2 -1
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5a908e851c07a69ab0aca84da9edb9c1f596112
|
4
|
+
data.tar.gz: d63b70c7f7bd0300e44bdee7b723c558e862ef62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4dfe2d85a96e772b193cfc8bd2986c2caf8e02e56fa1eb46ce86e9993d47b80d00a26eb243a1d6a3b51de56d8259a8de674217e99e89731e94a8665415c9c95
|
7
|
+
data.tar.gz: bd056b885c383fa246593a57685c61a2f33001ad1a120f5415d9d982272a03942af9bb9599976078912a424cf2626e0cb687fc8cce2064a728e175bad4b2ea99
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -16,6 +16,10 @@ moe.update_rates
|
|
16
16
|
# set the seconds after than the current rates are automatically expired
|
17
17
|
# by default, they never expire
|
18
18
|
moe.ttl_in_seconds = 86400
|
19
|
+
# (optional)
|
20
|
+
# use https to fetch rates from Open Exchange Rates
|
21
|
+
# disabled by default to support free-tier users
|
22
|
+
moe.secure_connection = true
|
19
23
|
# Store in cache
|
20
24
|
moe.save_rates
|
21
25
|
|
@@ -63,6 +67,7 @@ Dmitry Dedov (2)
|
|
63
67
|
chatgris (2)
|
64
68
|
Sam Lown (1)
|
65
69
|
Fabio Cantoni (1)
|
70
|
+
shpupti (1)
|
66
71
|
|
67
72
|
## License
|
68
73
|
|
@@ -5,15 +5,16 @@ require 'json'
|
|
5
5
|
|
6
6
|
class Money
|
7
7
|
module Bank
|
8
|
-
class InvalidCache < StandardError
|
8
|
+
class InvalidCache < StandardError; end
|
9
9
|
|
10
|
-
class NoAppId < StandardError
|
10
|
+
class NoAppId < StandardError; end
|
11
11
|
|
12
|
+
# OpenExchangeRatesBank base class
|
12
13
|
class OpenExchangeRatesBank < Money::Bank::VariableExchange
|
13
|
-
|
14
14
|
OER_URL = 'http://openexchangerates.org/latest.json'
|
15
|
+
SECURE_OER_URL = OER_URL.tr('http:', 'https:')
|
15
16
|
|
16
|
-
attr_accessor :cache, :app_id
|
17
|
+
attr_accessor :cache, :app_id, :secure_connection
|
17
18
|
attr_reader :doc, :oer_rates, :rates_expiration, :ttl_in_seconds
|
18
19
|
|
19
20
|
def ttl_in_seconds=(value)
|
@@ -27,16 +28,14 @@ class Money
|
|
27
28
|
currency = exchange_rate.first
|
28
29
|
next unless Money::Currency.find(currency)
|
29
30
|
set_rate('USD', currency, rate)
|
30
|
-
set_rate(currency, 'USD', 1.0/rate)
|
31
|
+
set_rate(currency, 'USD', 1.0 / rate)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
35
|
def save_rates
|
35
|
-
|
36
|
+
fail InvalidCache unless cache
|
36
37
|
text = read_from_url
|
37
|
-
if
|
38
|
-
store_in_cache(text)
|
39
|
-
end
|
38
|
+
store_in_cache(text) if valid_rates?(text)
|
40
39
|
rescue Errno::ENOENT
|
41
40
|
raise InvalidCache
|
42
41
|
end
|
@@ -47,10 +46,17 @@ class Money
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def expire_rates
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
return unless ttl_in_seconds
|
50
|
+
return if rates_expiration > Time.now
|
51
|
+
update_rates
|
52
|
+
refresh_rates_expiration
|
53
|
+
end
|
54
|
+
|
55
|
+
def source_url
|
56
|
+
fail NoAppId if app_id.nil? || app_id.empty?
|
57
|
+
oer_url = OER_URL
|
58
|
+
oer_url = SECURE_OER_URL if secure_connection
|
59
|
+
"#{oer_url}?app_id=#{app_id}"
|
54
60
|
end
|
55
61
|
|
56
62
|
protected
|
@@ -75,18 +81,13 @@ class Money
|
|
75
81
|
end
|
76
82
|
end
|
77
83
|
|
78
|
-
def source_url
|
79
|
-
raise NoAppId if app_id.nil? || app_id.empty?
|
80
|
-
"#{OER_URL}?app_id=#{app_id}"
|
81
|
-
end
|
82
|
-
|
83
84
|
def read_from_url
|
84
85
|
open(source_url).read
|
85
86
|
end
|
86
87
|
|
87
|
-
def
|
88
|
+
def valid_rates?(text)
|
88
89
|
parsed = JSON.parse(text)
|
89
|
-
parsed && parsed.
|
90
|
+
parsed && parsed.key?('rates')
|
90
91
|
rescue JSON::ParserError
|
91
92
|
false
|
92
93
|
end
|
@@ -4,123 +4,125 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
|
4
4
|
|
5
5
|
describe Money::Bank::OpenExchangeRatesBank do
|
6
6
|
subject { Money::Bank::OpenExchangeRatesBank.new }
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
let(:url) { Money::Bank::OpenExchangeRatesBank::OER_URL }
|
8
|
+
let(:secure_url) { Money::Bank::OpenExchangeRatesBank::SECURE_OER_URL }
|
9
|
+
let(:temp_cache_path) do
|
10
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'tmp.json'))
|
11
|
+
end
|
12
|
+
let(:cache_path) do
|
13
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'latest.json'))
|
10
14
|
end
|
11
15
|
|
12
16
|
describe 'exchange' do
|
13
17
|
before do
|
14
18
|
subject.app_id = TEST_APP_ID
|
15
|
-
|
16
|
-
subject.
|
17
|
-
stub(subject).read_from_url { File.read @cache_path }
|
19
|
+
subject.cache = temp_cache_path
|
20
|
+
stub(subject).read_from_url { File.read cache_path }
|
18
21
|
subject.save_rates
|
19
22
|
end
|
20
23
|
|
21
24
|
after do
|
22
|
-
File.unlink(
|
25
|
+
File.unlink(temp_cache_path)
|
23
26
|
end
|
24
27
|
|
25
|
-
describe
|
26
|
-
it
|
27
|
-
money = Money.new(0,
|
28
|
-
subject.exchange_with(money,
|
28
|
+
describe 'without rates' do
|
29
|
+
it 'able to exchange a money to its own currency even without rates' do
|
30
|
+
money = Money.new(0, 'USD')
|
31
|
+
subject.exchange_with(money, 'USD').must_equal money
|
29
32
|
end
|
30
33
|
|
31
|
-
it "
|
32
|
-
money = Money.new(0,
|
33
|
-
proc { subject.exchange_with(money,
|
34
|
+
it "raise if it can't find an exchange rate" do
|
35
|
+
money = Money.new(0, 'USD')
|
36
|
+
proc { subject.exchange_with(money, 'SSP') }
|
37
|
+
.must_raise Money::Bank::UnknownRate
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
|
-
describe
|
38
|
-
before do
|
41
|
+
describe 'with rates' do
|
42
|
+
before do
|
39
43
|
subject.update_rates
|
40
44
|
end
|
41
45
|
|
42
|
-
it
|
43
|
-
money = Money.new(100,
|
44
|
-
subject.exchange_with(money,
|
46
|
+
it 'should be able to exchange money from USD to a known exchange rate' do
|
47
|
+
money = Money.new(100, 'USD')
|
48
|
+
subject.exchange_with(money, 'BBD').must_equal Money.new(200, 'BBD')
|
45
49
|
end
|
46
50
|
|
47
|
-
it
|
48
|
-
money = Money.new(200,
|
49
|
-
subject.exchange_with(money,
|
51
|
+
it 'should be able to exchange money from a known exchange rate to USD' do
|
52
|
+
money = Money.new(200, 'BBD')
|
53
|
+
subject.exchange_with(money, 'USD').must_equal Money.new(100, 'USD')
|
50
54
|
end
|
51
55
|
|
52
56
|
it "should raise if it can't find an exchange rate" do
|
53
|
-
money = Money.new(0,
|
54
|
-
proc { subject.exchange_with(money,
|
57
|
+
money = Money.new(0, 'USD')
|
58
|
+
proc { subject.exchange_with(money, 'SSP') }
|
59
|
+
.must_raise Money::Bank::UnknownRate
|
55
60
|
end
|
56
61
|
end
|
57
|
-
|
58
62
|
end
|
59
63
|
|
60
64
|
describe 'update_rates' do
|
61
65
|
before do
|
62
66
|
subject.app_id = TEST_APP_ID
|
63
|
-
subject.cache =
|
67
|
+
subject.cache = cache_path
|
64
68
|
subject.update_rates
|
65
69
|
end
|
66
70
|
|
67
|
-
it
|
71
|
+
it 'should update itself with exchange rates from OpenExchangeRates' do
|
68
72
|
subject.oer_rates.keys.each do |currency|
|
69
73
|
next unless Money::Currency.find(currency)
|
70
|
-
subject.get_rate(
|
74
|
+
subject.get_rate('USD', currency).must_be :>, 0
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
74
|
-
it
|
78
|
+
it 'should not return 0 with integer rate' do
|
75
79
|
wtf = {
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
83
|
-
:
|
80
|
+
priority: 1,
|
81
|
+
iso_code: 'WTF',
|
82
|
+
name: 'WTF',
|
83
|
+
symbol: 'WTF',
|
84
|
+
subunit: 'Cent',
|
85
|
+
subunit_to_unit: 1000,
|
86
|
+
separator: '.',
|
87
|
+
delimiter: ','
|
84
88
|
}
|
85
89
|
Money::Currency.register(wtf)
|
86
|
-
subject.add_rate(
|
87
|
-
subject.add_rate(
|
90
|
+
subject.add_rate('USD', 'WTF', 2)
|
91
|
+
subject.add_rate('WTF', 'USD', 2)
|
88
92
|
subject.exchange_with(5000.to_money('WTF'), 'USD').cents.wont_equal 0
|
89
93
|
end
|
90
94
|
|
91
95
|
# in response to #4
|
92
|
-
it
|
96
|
+
it 'should exchange btc' do
|
93
97
|
btc = {
|
94
|
-
:
|
95
|
-
:
|
96
|
-
:
|
97
|
-
:
|
98
|
-
:
|
99
|
-
:
|
100
|
-
:
|
101
|
-
:
|
98
|
+
priority: 1,
|
99
|
+
iso_code: 'BTC',
|
100
|
+
name: 'Bitcoin',
|
101
|
+
symbol: 'BTC',
|
102
|
+
subunit: 'Cent',
|
103
|
+
subunit_to_unit: 1000,
|
104
|
+
separator: '.',
|
105
|
+
delimiter: ','
|
102
106
|
}
|
103
107
|
Money::Currency.register(btc)
|
104
108
|
rate = 13.7603
|
105
|
-
subject.add_rate(
|
106
|
-
subject.add_rate(
|
107
|
-
subject.exchange_with(100.to_money(
|
109
|
+
subject.add_rate('USD', 'BTC', 1 / 13.7603)
|
110
|
+
subject.add_rate('BTC', 'USD', rate)
|
111
|
+
subject.exchange_with(100.to_money('BTC'), 'USD').cents.must_equal 137_603
|
108
112
|
end
|
109
113
|
end
|
110
114
|
|
111
115
|
describe 'App ID' do
|
112
|
-
|
113
116
|
before do
|
114
|
-
|
115
|
-
|
116
|
-
stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
|
117
|
+
subject.cache = temp_cache_path
|
118
|
+
stub(OpenURI::OpenRead).open(url) { File.read cache_path }
|
117
119
|
end
|
118
120
|
|
119
121
|
it 'should raise an error if no App ID is set' do
|
120
122
|
proc { subject.save_rates }.must_raise Money::Bank::NoAppId
|
121
123
|
end
|
122
124
|
|
123
|
-
#TODO: As App IDs are compulsory soon, need to add more tests handle
|
125
|
+
# TODO: As App IDs are compulsory soon, need to add more tests handle
|
124
126
|
# app_id-specific errors from
|
125
127
|
# https://openexchangerates.org/documentation#errors
|
126
128
|
end
|
@@ -132,7 +134,7 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
132
134
|
end
|
133
135
|
|
134
136
|
it 'should get from url' do
|
135
|
-
stub(OpenURI::OpenRead).open(
|
137
|
+
stub(OpenURI::OpenRead).open(url) { File.read cache_path }
|
136
138
|
subject.update_rates
|
137
139
|
subject.oer_rates.wont_be_empty
|
138
140
|
end
|
@@ -142,14 +144,34 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
142
144
|
end
|
143
145
|
end
|
144
146
|
|
147
|
+
describe 'secure_connection' do
|
148
|
+
it "should use the non-secure http url if secure_connection isn't set" do
|
149
|
+
subject.secure_connection = nil
|
150
|
+
subject.app_id = TEST_APP_ID
|
151
|
+
subject.source_url.must_equal "#{url}?app_id=#{TEST_APP_ID}"
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should use the non-secure http url if secure_connection is false' do
|
155
|
+
subject.secure_connection = false
|
156
|
+
subject.app_id = TEST_APP_ID
|
157
|
+
subject.source_url.must_equal "#{url}?app_id=#{TEST_APP_ID}"
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should use the secure https url if secure_connection is set to true' do
|
161
|
+
subject.secure_connection = true
|
162
|
+
subject.app_id = TEST_APP_ID
|
163
|
+
subject.source_url.must_equal "#{secure_url}?app_id=#{TEST_APP_ID}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
145
167
|
describe 'no valid file for cache' do
|
146
168
|
before do
|
147
|
-
subject.cache = "space_dir#{rand(
|
169
|
+
subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
|
148
170
|
subject.app_id = TEST_APP_ID
|
149
171
|
end
|
150
172
|
|
151
173
|
it 'should get from url' do
|
152
|
-
stub(OpenURI::OpenRead).open(
|
174
|
+
stub(OpenURI::OpenRead).open(url) { File.read cache_path }
|
153
175
|
subject.update_rates
|
154
176
|
subject.oer_rates.wont_be_empty
|
155
177
|
end
|
@@ -161,40 +183,38 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
161
183
|
|
162
184
|
describe 'using proc for cache' do
|
163
185
|
before :each do
|
164
|
-
|
165
|
-
subject.cache =
|
186
|
+
@global_rates = nil
|
187
|
+
subject.cache = proc {|v|
|
166
188
|
if v
|
167
|
-
|
189
|
+
@global_rates = v
|
168
190
|
else
|
169
|
-
|
191
|
+
@global_rates
|
170
192
|
end
|
171
193
|
}
|
172
194
|
subject.app_id = TEST_APP_ID
|
173
195
|
end
|
174
196
|
|
175
197
|
it 'should get from url normally' do
|
176
|
-
stub(subject).source_url
|
198
|
+
stub(subject).source_url { cache_path }
|
177
199
|
subject.update_rates
|
178
200
|
subject.oer_rates.wont_be_empty
|
179
201
|
end
|
180
202
|
|
181
203
|
it 'should save from url and get from cache' do
|
182
|
-
stub(subject).source_url {
|
204
|
+
stub(subject).source_url { cache_path }
|
183
205
|
subject.save_rates
|
184
|
-
|
206
|
+
@global_rates.wont_be_empty
|
185
207
|
dont_allow(subject).source_url
|
186
208
|
subject.update_rates
|
187
209
|
subject.oer_rates.wont_be_empty
|
188
210
|
end
|
189
|
-
|
190
211
|
end
|
191
212
|
|
192
213
|
describe 'save rates' do
|
193
214
|
before do
|
194
215
|
subject.app_id = TEST_APP_ID
|
195
|
-
|
196
|
-
|
197
|
-
stub(OpenURI::OpenRead).open(Money::Bank::OpenExchangeRatesBank::OER_URL) { File.read @cache_path }
|
216
|
+
subject.cache = temp_cache_path
|
217
|
+
stub(OpenURI::OpenRead).open(url) { File.read cache_path }
|
198
218
|
subject.save_rates
|
199
219
|
end
|
200
220
|
|
@@ -202,33 +222,33 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
202
222
|
begin
|
203
223
|
subject.update_rates
|
204
224
|
rescue
|
205
|
-
assert false,
|
225
|
+
assert false, 'Should allow updating after saving'
|
206
226
|
end
|
207
227
|
end
|
208
228
|
|
209
|
-
it
|
210
|
-
initial_size = File.read(
|
211
|
-
stub(subject).read_from_url {
|
229
|
+
it 'should not break an existing file if save fails to read' do
|
230
|
+
initial_size = File.read(temp_cache_path).size
|
231
|
+
stub(subject).read_from_url { '' }
|
212
232
|
subject.save_rates
|
213
|
-
File.open(
|
233
|
+
File.open(temp_cache_path).read.size.must_equal initial_size
|
214
234
|
end
|
215
235
|
|
216
|
-
it
|
217
|
-
initial_size = File.read(
|
218
|
-
stub(subject).read_from_url {
|
236
|
+
it 'should not break an existing file if save returns json without rates' do
|
237
|
+
initial_size = File.read(temp_cache_path).size
|
238
|
+
stub(subject).read_from_url { '{"error": "An error"}' }
|
219
239
|
subject.save_rates
|
220
|
-
File.open(
|
240
|
+
File.open(temp_cache_path).read.size.must_equal initial_size
|
221
241
|
end
|
222
242
|
|
223
|
-
it
|
224
|
-
initial_size = File.read(
|
225
|
-
stub(subject).read_from_url {
|
243
|
+
it 'should not break an existing file if save returns a invalid json' do
|
244
|
+
initial_size = File.read(temp_cache_path).size
|
245
|
+
stub(subject).read_from_url { '{invalid_json: "An error"}' }
|
226
246
|
subject.save_rates
|
227
|
-
File.open(
|
247
|
+
File.open(temp_cache_path).read.size.must_equal initial_size
|
228
248
|
end
|
229
249
|
|
230
250
|
after do
|
231
|
-
File.delete
|
251
|
+
File.delete temp_cache_path
|
232
252
|
end
|
233
253
|
end
|
234
254
|
|
@@ -238,9 +258,10 @@ describe Money::Bank::OpenExchangeRatesBank do
|
|
238
258
|
subject.ttl_in_seconds = 1000
|
239
259
|
@usd_eur_rate = 0.655
|
240
260
|
subject.add_rate('USD', 'EUR', @usd_eur_rate)
|
241
|
-
|
242
|
-
|
243
|
-
|
261
|
+
subject.cache = temp_cache_path
|
262
|
+
stub(OpenURI::OpenRead).open(url) do
|
263
|
+
File.read cache_path
|
264
|
+
end
|
244
265
|
end
|
245
266
|
|
246
267
|
describe 'when the ttl has expired' do
|
data/test/test_helper.rb
CHANGED
@@ -10,5 +10,6 @@ TEST_APP_ID_PATH = File.join(File.dirname(__FILE__), '..', 'TEST_APP_ID')
|
|
10
10
|
TEST_APP_ID = ENV['TEST_APP_ID'] || File.read(TEST_APP_ID_PATH)
|
11
11
|
|
12
12
|
if TEST_APP_ID.nil? || TEST_APP_ID.empty?
|
13
|
-
|
13
|
+
fail "Please add a valid app id to file #{TEST_APP_ID_PATH} or to " \
|
14
|
+
' TEST_APP_ID environment'
|
14
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money-open-exchange-rates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Arnoud
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: money
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: json
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: A gem that calculates the exchange rate using published rates from open-exchange-rates.
|
126
140
|
Compatible with the money gem.
|
127
141
|
email: laurent@spkdev.net
|
@@ -149,7 +163,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
163
|
requirements:
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version: 1.9.
|
166
|
+
version: 1.9.3
|
153
167
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
168
|
requirements:
|
155
169
|
- - ">="
|