money-open-exchange-rates 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca083dbf9fddf98dd83014ecfdd3ab592d30edcfe96b9a6b6086b66f5e99eadd
4
- data.tar.gz: 3c3e381dea270d1c19843833d2c426ced7e8986678adb3ea1e3b2efcc521751d
3
+ metadata.gz: 533cde694f8fe852a3c3876b5433871d66aa438184978a676296c93867d58132
4
+ data.tar.gz: 793cc9510199cd8d4da3ac1e05bbd133e154ed167eeee585feeadeb403b0cc36
5
5
  SHA512:
6
- metadata.gz: 4737d36a2bc739e9594ab3a1ecf744c242aa694c3c44f216c17969cfa188f9e635621c28dea9ba12abcf1a8e1eb6af4ffb59f3a828d10beeac678da70a003c68
7
- data.tar.gz: 80aa7a95bf5b2d01562726b0cbbe95b7dbae45103632cf722469a03b521d75c284112970f8bd5603de5be7895bea890d605c061559ab94db0aa5ea85200896e2
6
+ metadata.gz: b8d3e1d9900ebd8ecd0c9d2c723697c0d0ba9416f9ac82c4dc0ed8264ca8822571c841441d445e55ef655fece32019a5c8c397f83ffd27766689128edbe54f0b
7
+ data.tar.gz: 78cb56294a55bc7f6b7f7daff77f4f5acae9cacd9516cb9e5b85544674ed3dd56898b2d138b2c5af2623ab3d583dc26af55b79d4d4437e4c915ca68b2586aca3
data/History.md CHANGED
@@ -1,4 +1,11 @@
1
1
 
2
+ v1.2.0 / 2018-03-31
3
+ ===================
4
+
5
+ * Merge pull request #51 from spk/fix-expire_rates
6
+ * Add force_refresh_rate_on_expire option and use api timestamp
7
+ * README: info currency-exchange
8
+
2
9
  v1.1.1 / 2018-03-30
3
10
  ===================
4
11
 
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Money Open Exchange Rates
2
2
 
3
3
  A gem that calculates the exchange rate using published rates from
4
- [open-exchange-rates](https://openexchangerates.org/)
4
+ [open-exchange-rates](https://openexchangerates.org/). Compatible with
5
+ [Money](https://github.com/RubyMoney/money#currency-exchange) [currency
6
+ exchange](http://www.rubydoc.info/gems/money/Money/Bank/VariableExchange).
5
7
 
6
8
  Check [api documentation](https://docs.openexchangerates.org/)
7
9
 
@@ -42,37 +44,56 @@ gem install money-open-exchange-rates
42
44
 
43
45
  ~~~ ruby
44
46
  require 'money/bank/open_exchange_rates_bank'
47
+
48
+ # Memory store per default; for others just pass as argument a class like
49
+ # explained in https://github.com/RubyMoney/money#exchange-rate-stores
45
50
  oxr = Money::Bank::OpenExchangeRatesBank.new
46
- # see https://github.com/spk/money-open-exchange-rates#cache for more info
47
- oxr.cache = 'path/to/file/cache.json'
48
51
  oxr.app_id = 'your app id from https://openexchangerates.org/signup'
49
52
  oxr.update_rates
50
53
 
51
54
  # (optional)
52
- # set the seconds after than the current rates are automatically expired
55
+ # See https://github.com/spk/money-open-exchange-rates#cache for more info
56
+ # Updated only when `refresh_rates` is called
57
+ oxr.cache = 'path/to/file/cache.json'
58
+
59
+ # (optional)
60
+ # Set the seconds after than the current rates are automatically expired
53
61
  # by default, they never expire, in this example 1 day.
62
+ # This ttl is about money store (memory, database ...) passed though
63
+ # `Money::Bank::OpenExchangeRatesBank` as argument not about `cache` option.
64
+ # The base time is the timestamp fetched from API.
54
65
  oxr.ttl_in_seconds = 86400
66
+
55
67
  # (optional)
56
- # set historical date of the rate
68
+ # Set historical date of the rate
57
69
  # see https://openexchangerates.org/documentation#historical-data
58
70
  oxr.date = '2015-01-01'
71
+
59
72
  # (optional)
60
73
  # Set the base currency for all rates. By default, USD is used.
61
74
  # OpenExchangeRates only allows USD as base currency
62
75
  # for the free plan users.
63
76
  oxr.source = 'USD'
77
+
64
78
  # (optional)
65
79
  # Extend returned values with alternative, black market and digital currency
66
80
  # rates. By default, false is used
67
81
  # see: https://docs.openexchangerates.org/docs/alternative-currencies
68
82
  oxr.show_alternative = true
83
+
69
84
  # (optional)
70
- # Store in cache
71
- # Force rates storage in cache, this is done automaticly after TTL is expire.
85
+ # Refresh rates, store in cache and update rates
86
+ # Should be used on crontab/worker/scheduler `Money.default_bank.refresh_rates`
72
87
  # If you are using unicorn-worker-killer gem or on Heroku like platform,
73
88
  # you should avoid to put this on the initializer of your Rails application,
74
89
  # because will increase your OXR API usage.
75
- oxr.save_rates
90
+ oxr.refresh_rates
91
+
92
+ # (optional)
93
+ # Force refresh rates cache and store on the fly when ttl is expired
94
+ # This will slow down request on get_rate, so use at your on risk, if you don't
95
+ # want to setup crontab/worker/scheduler for your application
96
+ oxr.force_refresh_rate_on_expire = true
76
97
 
77
98
  Money.default_bank = oxr
78
99
 
@@ -109,22 +130,32 @@ oxr.cache = Proc.new do |text|
109
130
  end
110
131
  ~~~
111
132
 
133
+ To update the cache call `Money.default_bank.refresh_rates` on
134
+ crontab/worker/scheduler. This have to be done this way because the fetch can
135
+ take some time (HTTP call) and can fail.
136
+
112
137
  ## Full example configuration initializer with Rails and cache
113
138
 
114
139
  ~~~ ruby
115
140
  require 'money/bank/open_exchange_rates_bank'
116
141
 
117
142
  OXR_CACHE_KEY = 'money:exchange_rates'.freeze
118
- oxr = Money::Bank::OpenExchangeRatesBank.new
143
+ # ExchangeRate is an ActiveRecord model
144
+ # more info at https://github.com/RubyMoney/money#exchange-rate-stores
145
+ oxr = Money::Bank::OpenExchangeRatesBank.new(ExchangeRate)
119
146
  oxr.ttl_in_seconds = 86400
120
147
  oxr.cache = Proc.new do |text|
121
148
  if text
149
+ # only expire when refresh_rates is called or `force_refresh_rate_on_expire`
150
+ # option is enabled
151
+ # you can also set `expires_in` option on write to force fetch new rates
122
152
  Rails.cache.write(OXR_CACHE_KEY, text)
123
153
  else
124
154
  Rails.cache.read(OXR_CACHE_KEY)
125
155
  end
126
156
  end
127
157
  oxr.app_id = ENV['OXR_API_KEY']
158
+ oxr.show_alternative = true
128
159
  oxr.update_rates
129
160
 
130
161
  Money.default_bank = oxr
@@ -28,6 +28,8 @@ class Money
28
28
 
29
29
  # Default base currency "base": "USD"
30
30
  OE_SOURCE = 'USD'.freeze
31
+ RATES_KEY = 'rates'.freeze
32
+ TIMESTAMP_KEY = 'timestamp'.freeze
31
33
 
32
34
  # @deprecated secure_connection is deprecated and has no effect
33
35
  def secure_connection=(*)
@@ -64,6 +66,13 @@ class Money
64
66
  # @return [String] The requested date in YYYY-MM-DD format
65
67
  attr_accessor :date
66
68
 
69
+ # Force refresh rates cache and store on the fly when ttl is expired
70
+ # This will slow down request on get_rate, so use at your on risk, if you
71
+ # don't want to setup crontab/worker/scheduler for your application
72
+ #
73
+ # @param [Boolean]
74
+ attr_accessor :force_refresh_rate_on_expire
75
+
67
76
  # Rates expiration Time
68
77
  #
69
78
  # @return [Time] expiration time
@@ -93,6 +102,20 @@ class Money
93
102
  # @return [Boolean] Setted show alternative
94
103
  attr_writer :show_alternative
95
104
 
105
+ # Set current rates timestamp
106
+ #
107
+ # @return [Time]
108
+ def rates_timestamp=(at)
109
+ @rates_timestamp = Time.at(at)
110
+ end
111
+
112
+ # Current rates timestamp
113
+ #
114
+ # @return [Time]
115
+ def rates_timestamp
116
+ @rates_timestamp || Time.at(0)
117
+ end
118
+
96
119
  # Set the seconds after than the current rates are automatically expired
97
120
  # by default, they never expire.
98
121
  #
@@ -144,17 +167,6 @@ class Money
144
167
  end
145
168
  end
146
169
 
147
- # Save rates on cache
148
- # Can raise InvalidCache
149
- #
150
- # @return [Proc,File]
151
- def save_rates
152
- return nil unless cache
153
- store_in_cache(@json_response) if valid_rates?(@json_response)
154
- rescue Errno::ENOENT
155
- raise InvalidCache
156
- end
157
-
158
170
  # Alias super method
159
171
  alias super_get_rate get_rate
160
172
 
@@ -171,13 +183,23 @@ class Money
171
183
  rate || calc_pair_rate_using_base(from_currency, to_currency, opts)
172
184
  end
173
185
 
186
+ # Fetch from url and save cache
187
+ #
188
+ # @return [Array] Array of exchange rates
189
+ def refresh_rates
190
+ read_from_url
191
+ end
192
+
193
+ # Alias refresh_rates method
194
+ alias save_rates refresh_rates
195
+
174
196
  # Expire rates when expired
175
197
  #
176
198
  # @return [NilClass, Time] nil if not expired or new expiration time
177
199
  def expire_rates
178
200
  return unless ttl_in_seconds
179
201
  return if rates_expiration > Time.now
180
- read_from_url
202
+ refresh_rates if force_refresh_rate_on_expire
181
203
  update_rates
182
204
  refresh_rates_expiration
183
205
  end
@@ -205,6 +227,16 @@ class Money
205
227
 
206
228
  protected
207
229
 
230
+ # Save rates on cache
231
+ # Can raise InvalidCache
232
+ #
233
+ # @return [Proc,File]
234
+ def save_cache
235
+ store_in_cache(@json_response) if valid_rates?(@json_response)
236
+ rescue Errno::ENOENT
237
+ raise InvalidCache
238
+ end
239
+
208
240
  # Latest url if no date given
209
241
  #
210
242
  # @return [String] URL
@@ -266,7 +298,7 @@ class Money
266
298
  def read_from_url
267
299
  raise NoAppId if app_id.nil? || app_id.empty?
268
300
  @json_response = open(source_url).read
269
- save_rates
301
+ save_cache if cache
270
302
  @json_response
271
303
  end
272
304
 
@@ -280,7 +312,7 @@ class Money
280
312
  def valid_rates?(text)
281
313
  return false unless text
282
314
  parsed = JSON.parse(text)
283
- parsed && parsed.key?('rates')
315
+ parsed && parsed.key?(RATES_KEY) && parsed.key?(TIMESTAMP_KEY)
284
316
  rescue JSON::ParserError
285
317
  false
286
318
  end
@@ -290,14 +322,15 @@ class Money
290
322
  # @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
291
323
  def exchange_rates
292
324
  doc = JSON.parse(read_from_cache || read_from_url)
293
- @oer_rates = doc['rates']
325
+ self.rates_timestamp = doc[TIMESTAMP_KEY]
326
+ @oer_rates = doc[RATES_KEY]
294
327
  end
295
328
 
296
329
  # Refresh expiration from now
297
330
  #
298
331
  # @return [Time] new expiration time
299
332
  def refresh_rates_expiration
300
- @rates_expiration = Time.now + ttl_in_seconds
333
+ @rates_expiration = rates_timestamp + ttl_in_seconds
301
334
  end
302
335
 
303
336
  # Get rate or calculate it as inverse rate
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Module for version constant
4
4
  module OpenExchangeRatesBank
5
- VERSION = '1.1.1'.freeze
5
+ VERSION = '1.2.0'.freeze
6
6
  end
@@ -23,7 +23,6 @@ describe Money::Bank::OpenExchangeRatesBank do
23
23
  add_to_webmock(subject)
24
24
  subject.cache = temp_cache_path
25
25
  subject.update_rates
26
- subject.save_rates
27
26
  end
28
27
 
29
28
  after do
@@ -148,13 +147,10 @@ describe Money::Bank::OpenExchangeRatesBank do
148
147
  end
149
148
 
150
149
  it 'should get from url' do
150
+ dont_allow(subject).save_cache
151
151
  subject.update_rates
152
152
  subject.oer_rates.wont_be_empty
153
153
  end
154
-
155
- it 'should return nil when cache is nil' do
156
- subject.save_rates.must_equal nil
157
- end
158
154
  end
159
155
 
160
156
  describe 'secure_connection' do
@@ -197,7 +193,7 @@ describe Money::Bank::OpenExchangeRatesBank do
197
193
  add_to_webmock(subject)
198
194
  end
199
195
 
200
- it 'should raise an error if invalid path is given to save_rates' do
196
+ it 'should raise an error if invalid path is given to update_rates' do
201
197
  proc { subject.update_rates }.must_raise Money::Bank::InvalidCache
202
198
  end
203
199
  end
@@ -221,7 +217,6 @@ describe Money::Bank::OpenExchangeRatesBank do
221
217
  end
222
218
 
223
219
  it 'should save from url and get from cache' do
224
- subject.save_rates
225
220
  @global_rates.wont_be_empty
226
221
  dont_allow(subject).source_url
227
222
  subject.update_rates
@@ -229,12 +224,11 @@ describe Money::Bank::OpenExchangeRatesBank do
229
224
  end
230
225
  end
231
226
 
232
- describe 'save rates' do
227
+ describe '#refresh_rates' do
233
228
  before do
234
229
  add_to_webmock(subject)
235
230
  subject.cache = temp_cache_path
236
231
  subject.update_rates
237
- subject.save_rates
238
232
  end
239
233
 
240
234
  after do
@@ -243,7 +237,7 @@ describe Money::Bank::OpenExchangeRatesBank do
243
237
 
244
238
  it 'should allow update after save' do
245
239
  begin
246
- subject.update_rates
240
+ subject.refresh_rates
247
241
  rescue
248
242
  assert false, 'Should allow updating after saving'
249
243
  end
@@ -252,31 +246,45 @@ describe Money::Bank::OpenExchangeRatesBank do
252
246
  it 'should not break an existing file if save fails to read' do
253
247
  initial_size = File.read(temp_cache_path).size
254
248
  stub(subject).read_from_url { '' }
255
- subject.save_rates
249
+ subject.refresh_rates
256
250
  File.open(temp_cache_path).read.size.must_equal initial_size
257
251
  end
258
252
 
259
253
  it 'should not break an existing file if save returns json without rates' do
260
254
  initial_size = File.read(temp_cache_path).size
261
255
  stub(subject).read_from_url { '{"error": "An error"}' }
262
- subject.save_rates
256
+ subject.refresh_rates
263
257
  File.open(temp_cache_path).read.size.must_equal initial_size
264
258
  end
265
259
 
266
260
  it 'should not break an existing file if save returns a invalid json' do
267
261
  initial_size = File.read(temp_cache_path).size
268
262
  stub(subject).read_from_url { '{invalid_json: "An error"}' }
269
- subject.save_rates
263
+ subject.refresh_rates
270
264
  File.open(temp_cache_path).read.size.must_equal initial_size
271
265
  end
272
266
  end
273
267
 
268
+ describe '#rates_timestamp' do
269
+ before do
270
+ add_to_webmock(subject)
271
+ end
272
+
273
+ it 'should be set on update_rates' do
274
+ subject.update_rates
275
+ subject.rates_timestamp.must_equal Time.at(1_414_008_044)
276
+ end
277
+ end
278
+
274
279
  describe '#expire_rates' do
275
280
  before do
276
281
  add_to_webmock(subject)
277
- subject.ttl_in_seconds = 1000
282
+ # see test/data/latest.json +4
283
+ subject.rates_timestamp = 1_414_008_044
284
+ @ttl_in_seconds = 1000
285
+ subject.ttl_in_seconds = @ttl_in_seconds
278
286
  @old_usd_eur_rate = 0.655
279
- # see test/latest.json +52
287
+ # see test/data/latest.json +52
280
288
  @new_usd_eur_rate = 0.79085
281
289
  subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
282
290
  @global_rates = nil
@@ -291,47 +299,57 @@ describe Money::Bank::OpenExchangeRatesBank do
291
299
 
292
300
  describe 'when the ttl has expired' do
293
301
  it 'should update the rates' do
294
- subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
295
- Timecop.freeze(Time.now + 1001) do
302
+ Timecop.freeze(subject.rates_timestamp) do
303
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
304
+ end
305
+ Timecop.freeze(subject.rates_timestamp + (@ttl_in_seconds + 1)) do
296
306
  subject.get_rate('USD', 'EUR').wont_equal @old_usd_eur_rate
297
307
  subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
298
308
  end
299
309
  end
300
310
 
301
311
  it 'should save rates' do
302
- subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
303
- Timecop.freeze(Time.now + 1001) do
304
- subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
305
- @global_rates.wont_be_empty
312
+ Timecop.freeze(subject.rates_timestamp) do
313
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
306
314
  end
307
- end
308
-
309
- it 'should save rates and refresh it when cache is invalid' do
310
- subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
311
- Timecop.freeze(Time.now + 1001) do
312
- @global_rates = []
315
+ Timecop.freeze(subject.rates_timestamp + (@ttl_in_seconds + 1)) do
313
316
  subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
314
317
  @global_rates.wont_be_empty
315
318
  end
316
319
  end
317
320
 
318
321
  it 'updates the next expiration time' do
319
- Timecop.freeze(Time.now + 1001) do
320
- exp_time = Time.now + 1000
322
+ Timecop.freeze(subject.rates_timestamp + (@ttl_in_seconds + 1)) do
323
+ exp_time = subject.rates_timestamp + @ttl_in_seconds
321
324
  subject.expire_rates
322
325
  subject.rates_expiration.must_equal exp_time
323
326
  end
324
327
  end
328
+
329
+ describe '#force_refresh_rate_on_expire' do
330
+ it 'should save rates and force refresh' do
331
+ subject.force_refresh_rate_on_expire = true
332
+ Timecop.freeze(subject.rates_timestamp) do
333
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
334
+ end
335
+ Timecop.freeze(Time.now + 1001) do
336
+ @global_rates = []
337
+ subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
338
+ @global_rates.wont_be_empty
339
+ end
340
+ end
341
+ end
325
342
  end
326
343
 
327
344
  describe 'when the ttl has not expired' do
328
- it 'not should update the rates' do
345
+ it 'should not update the rates' do
329
346
  exp_time = subject.rates_expiration
330
- dont_allow(subject).read_from_url
331
- dont_allow(subject).update_rates
332
- dont_allow(subject).refresh_rates_expiration
333
- subject.expire_rates
334
- subject.rates_expiration.must_equal exp_time
347
+ Timecop.freeze(subject.rates_timestamp) do
348
+ dont_allow(subject).update_rates
349
+ dont_allow(subject).refresh_rates_expiration
350
+ subject.expire_rates
351
+ subject.rates_expiration.must_equal exp_time
352
+ end
335
353
  end
336
354
  end
337
355
  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: 1.1.1
4
+ version: 1.2.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: 2018-03-30 00:00:00.000000000 Z
11
+ date: 2018-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: money