money-open-exchange-rates 1.1.1 → 1.4.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: 213847dd55f3bbc717a20dc056e61510f4b00ede31bce5b1c7022b16eedc875e
4
+ data.tar.gz: e245e87886f4c2fc2d8ae6fcf25c80d5f7797ffc3d877eaafab18abd2f911050
5
5
  SHA512:
6
- metadata.gz: 4737d36a2bc739e9594ab3a1ecf744c242aa694c3c44f216c17969cfa188f9e635621c28dea9ba12abcf1a8e1eb6af4ffb59f3a828d10beeac678da70a003c68
7
- data.tar.gz: 80aa7a95bf5b2d01562726b0cbbe95b7dbae45103632cf722469a03b521d75c284112970f8bd5603de5be7895bea890d605c061559ab94db0aa5ea85200896e2
6
+ metadata.gz: 7a63501cb0baad531f152bedc2a232697f9c64da9bfeef9dd576ecbcda9dceaed83aba902f272c5cd4d44af20e9a55bd7654e1c244a60a5620b9db6f033c6f4b
7
+ data.tar.gz: 0c5466657cac8fb4e826d7d9e24ac354778eca529ac156385d3dc44070ed78d3e9278ffc2e17ccad705311195fdaa2822d17855cbcd2e721d90cab37ebe1c0c2
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
6
+
7
+ gem 'simplecov', require: false
data/History.md CHANGED
@@ -1,4 +1,71 @@
1
1
 
2
+ 1.4.0 / 2020-09-11
3
+ ==================
4
+
5
+ * Fix jruby test for cross courses
6
+ * Fix deprecation global use of must_equal
7
+ * Merge pull request #66 from @korun / clear-cross-courses
8
+ * Update rates inside transaction, to prevent RC issues
9
+ * Merge pull request #65 from @korun / ensure-file-closed
10
+ * Fix unclosed file descriptor after read_from_cache
11
+ * Merge pull request #64 from @anton-smagin / clear-cross-courses
12
+ * Set old rates to nil on #update_rates before set new one to expire pair rates using base
13
+ * Add coverage stage on ci
14
+ * Remove Ruby 2.3 support
15
+ * Switch to gitlab ci
16
+ * Update README
17
+ * Fix rubocop to 0.76.0 and remove minitest-focus
18
+ * Update gitlab ci gem install does not have --no-ri anymore
19
+ * README add more doc about update_rates
20
+ * Fix deprecation for minitest 6
21
+ * Fix flaky test about Money::Bank::NoAppId
22
+ * Support Pathname for cache
23
+ * Update rubocop to 0.76
24
+ * Remove Ruby 2.2 support and update rubocop
25
+ * Update travis list
26
+ * README: improve refresh doc
27
+
28
+ 1.3.0 / 2019-01-20
29
+ ==================
30
+
31
+ * Update webmock to 3.5
32
+ * Update rubocop to 0.63
33
+ * Switch from rr to mocha/minitest
34
+ * Add prettyprint option
35
+ * README: add info about tests
36
+ * Remove deprecated secure_connection= option
37
+ * Merge pull request #54 from @LinkUpStudioUA / symbols-doc-link-fix
38
+ * Fix link to filtering by symbols doc
39
+ * Fix bundler to <2 on travis (dropped support Ruby < 2.3)
40
+ * Fix bundler to 1.17.3 (2.0 dropped support Ruby < 2.3)
41
+ * Merge pull request #52 from @thejchap / feature/symbols
42
+ * add support for symbols query param
43
+ * Remove ruby 2.0/2.1 support and fix rubocop offenses
44
+ * Add minitest-focus gem
45
+ * Avoid redefine json_response method
46
+
47
+ v1.2.2 / 2018-03-31
48
+ ===================
49
+
50
+ * Warn secure_connection= is deprecated
51
+ * More simple code on calc_pair_rate_using_base
52
+ * Increase code coverage on source_url
53
+ * Fix parse error specs on refresh_rates
54
+ * Use Coveralls for coverage
55
+ * Add tests for rates_timestamp issue
56
+
57
+ v1.2.1 / 2018-03-31
58
+ ===================
59
+
60
+ * Fix rates_timestamp should be now per default
61
+
62
+ v1.2.0 / 2018-03-31
63
+ ===================
64
+
65
+ * Merge pull request #51 from spk/fix-expire_rates
66
+ * Add force_refresh_rate_on_expire option and use api timestamp
67
+ * README: info currency-exchange
68
+
2
69
  v1.1.1 / 2018-03-30
3
70
  ===================
4
71
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2011-2018 Laurent Arnoud <laurent@spkdev.net>
3
+ Copyright (c) 2011-2020 Laurent Arnoud <laurent@spkdev.net>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
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
 
@@ -22,69 +24,125 @@ Check [api documentation](https://docs.openexchangerates.org/)
22
24
 
23
25
  Add this line to your application's Gemfile:
24
26
 
25
- ~~~ ruby
27
+ ``` ruby
26
28
  gem 'money-open-exchange-rates'
27
- ~~~
29
+ ```
28
30
 
29
31
  And then execute:
30
32
 
31
- ~~~
33
+ ```
32
34
  bundle
33
- ~~~
35
+ ```
34
36
 
35
37
  Or install it yourself as:
36
38
 
37
- ~~~
39
+ ```
38
40
  gem install money-open-exchange-rates
39
- ~~~
41
+ ```
40
42
 
41
43
  ## Usage
42
44
 
43
- ~~~ ruby
45
+ ``` ruby
44
46
  require 'money/bank/open_exchange_rates_bank'
45
- 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'
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
50
+ oxr = Money::Bank::OpenExchangeRatesBank.new(Money::RatesStore::Memory.new)
48
51
  oxr.app_id = 'your app id from https://openexchangerates.org/signup'
52
+
53
+ # Update the rates for the current rates storage
54
+ # If the storage is memory you will have to restart the server to be taken into
55
+ # account.
56
+ # If the storage is a database, file, this can be added to
57
+ # crontab/worker/scheduler `Money.default_bank.update_rates`
49
58
  oxr.update_rates
50
59
 
51
60
  # (optional)
52
- # set the seconds after than the current rates are automatically expired
61
+ # See https://github.com/spk/money-open-exchange-rates#cache for more info
62
+ # Updated only when `refresh_rates` is called
63
+ oxr.cache = 'path/to/file/cache.json'
64
+
65
+ # (optional)
66
+ # Set the seconds after than the current rates are automatically expired
53
67
  # by default, they never expire, in this example 1 day.
68
+ # This ttl is about money store (memory, database ...) passed though
69
+ # `Money::Bank::OpenExchangeRatesBank` as argument not about `cache` option.
70
+ # The base time is the timestamp fetched from API.
54
71
  oxr.ttl_in_seconds = 86400
72
+
55
73
  # (optional)
56
- # set historical date of the rate
74
+ # Set historical date of the rate
57
75
  # see https://openexchangerates.org/documentation#historical-data
58
76
  oxr.date = '2015-01-01'
77
+
59
78
  # (optional)
60
79
  # Set the base currency for all rates. By default, USD is used.
61
80
  # OpenExchangeRates only allows USD as base currency
62
81
  # for the free plan users.
63
82
  oxr.source = 'USD'
83
+
64
84
  # (optional)
65
85
  # Extend returned values with alternative, black market and digital currency
66
86
  # rates. By default, false is used
67
87
  # see: https://docs.openexchangerates.org/docs/alternative-currencies
68
88
  oxr.show_alternative = true
89
+
69
90
  # (optional)
70
- # Store in cache
71
- # Force rates storage in cache, this is done automaticly after TTL is expire.
91
+ # Minified Response ('prettyprint')
92
+ # see https://docs.openexchangerates.org/docs/prettyprint
93
+ oxr.prettyprint = false
94
+
95
+ # (optional)
96
+ # Refresh rates, store in cache and update rates
97
+ # Should be used on crontab/worker/scheduler `Money.default_bank.refresh_rates`
72
98
  # If you are using unicorn-worker-killer gem or on Heroku like platform,
73
99
  # you should avoid to put this on the initializer of your Rails application,
74
100
  # because will increase your OXR API usage.
75
- oxr.save_rates
101
+ oxr.refresh_rates
102
+
103
+ # (optional)
104
+ # Force refresh rates cache and store on the fly when ttl is expired
105
+ # This will slow down request on get_rate, so use at your on risk, if you don't
106
+ # want to setup crontab/worker/scheduler for your application.
107
+ # Again this is not safe with multiple servers and could increase API usage.
108
+ oxr.force_refresh_rate_on_expire = true
76
109
 
77
110
  Money.default_bank = oxr
78
111
 
79
112
  Money.default_bank.get_rate('USD', 'CAD')
80
- ~~~
113
+ ```
114
+
115
+ ## Refresh rates
116
+
117
+ ### With [whenever](https://github.com/javan/whenever)
118
+
119
+ ``` ruby
120
+ every :hour do
121
+ runner "Money.default_bank.refresh_rates"
122
+ # you will have to restart the server if you are using memory rate store
123
+ runner "Money.default_bank.update_rates"
124
+ end
125
+ ```
126
+
127
+ ### With rake task
128
+
129
+ ``` ruby
130
+ namespace :open_exchange_rates do
131
+ desc "Refresh rates from cache and update rates"
132
+ task :refresh_rates => :environment do
133
+ Money.default_bank.refresh_rates
134
+ # you will have to restart the server if you are using memory rate store
135
+ Money.default_bank.update_rates
136
+ end
137
+ end
138
+ ```
81
139
 
82
140
  ## Cache
83
141
 
84
142
  You can also provide a `Proc` as a cache to provide your own caching mechanism
85
143
  perhaps with Redis or just a thread safe `Hash` (global). For example:
86
144
 
87
- ~~~ ruby
145
+ ``` ruby
88
146
  oxr.cache = Proc.new do |v|
89
147
  key = 'money:exchange_rates'
90
148
  if v
@@ -93,12 +151,12 @@ oxr.cache = Proc.new do |v|
93
151
  Thread.current[key]
94
152
  end
95
153
  end
96
- ~~~
154
+ ```
97
155
 
98
156
  With `Rails` cache example:
99
157
 
100
- ~~~ ruby
101
- OXR_CACHE_KEY = 'money:exchange_rates'.freeze
158
+ ``` ruby
159
+ OXR_CACHE_KEY = "#{Rails.env}:money:exchange_rates".freeze
102
160
  oxr.ttl_in_seconds = 86400
103
161
  oxr.cache = Proc.new do |text|
104
162
  if text
@@ -107,28 +165,63 @@ oxr.cache = Proc.new do |text|
107
165
  Rails.cache.read(OXR_CACHE_KEY)
108
166
  end
109
167
  end
110
- ~~~
168
+ ```
169
+
170
+ To update the cache call `Money.default_bank.refresh_rates` on
171
+ crontab/worker/scheduler. This have to be done this way because the fetch can
172
+ take some time (HTTP call) and can fail.
111
173
 
112
174
  ## Full example configuration initializer with Rails and cache
113
175
 
114
- ~~~ ruby
176
+ ``` ruby
115
177
  require 'money/bank/open_exchange_rates_bank'
116
178
 
117
- OXR_CACHE_KEY = 'money:exchange_rates'.freeze
118
- oxr = Money::Bank::OpenExchangeRatesBank.new
179
+ OXR_CACHE_KEY = "#{Rails.env}:money:exchange_rates".freeze
180
+ # ExchangeRate is an ActiveRecord model
181
+ # more info at https://github.com/RubyMoney/money#exchange-rate-stores
182
+ oxr = Money::Bank::OpenExchangeRatesBank.new(ExchangeRate)
119
183
  oxr.ttl_in_seconds = 86400
120
184
  oxr.cache = Proc.new do |text|
121
185
  if text
186
+ # only expire when refresh_rates is called or `force_refresh_rate_on_expire`
187
+ # option is enabled
188
+ # you can also set `expires_in` option on write to force fetch new rates
122
189
  Rails.cache.write(OXR_CACHE_KEY, text)
123
190
  else
124
191
  Rails.cache.read(OXR_CACHE_KEY)
125
192
  end
126
193
  end
127
194
  oxr.app_id = ENV['OXR_API_KEY']
195
+ oxr.show_alternative = true
196
+ oxr.prettyprint = false
197
+
198
+ # This can be removed if you have data to avoid http call on boot for production
128
199
  oxr.update_rates
129
200
 
130
201
  Money.default_bank = oxr
131
- ~~~
202
+ ```
203
+
204
+ See also how to [refresh and update rates](#refresh-rates)
205
+
206
+ ### Tests
207
+
208
+ To avoid to hit the API we can use the cache option with a saved file like this:
209
+
210
+ ``` ruby
211
+ OXR_CACHE_KEY = "#{Rails.env}:money:exchange_rates".freeze
212
+ if Rails.env.test?
213
+ oxr.cache = Rails.root.join("test/fixtures/currency-rates.json").to_s
214
+ else
215
+ oxr.ttl_in_seconds = 5.minutes.to_i
216
+ oxr.cache = Proc.new do |text|
217
+ if text
218
+ Rails.cache.write(OXR_CACHE_KEY, text)
219
+ else
220
+ Rails.cache.read(OXR_CACHE_KEY)
221
+ end
222
+ end
223
+ end
224
+ ```
132
225
 
133
226
  ## Pair rates
134
227
 
@@ -137,9 +230,9 @@ or using base currency rate to both currencies forming the pair.
137
230
 
138
231
  ## Tests
139
232
 
140
- ~~~
233
+ ```
141
234
  bundle exec rake
142
- ~~~
235
+ ```
143
236
 
144
237
  ## Refs
145
238
 
@@ -156,12 +249,12 @@ See [GitHub](https://github.com/spk/money-open-exchange-rates/graphs/contributor
156
249
 
157
250
  The MIT License
158
251
 
159
- Copyright © 2011-2018 Laurent Arnoud <laurent@spkdev.net>
252
+ Copyright © 2011-2020 Laurent Arnoud <laurent@spkdev.net>
160
253
 
161
254
  ---
162
- [![Build](https://img.shields.io/travis-ci/spk/money-open-exchange-rates.svg)](https://travis-ci.org/spk/money-open-exchange-rates)
255
+ [![Build](https://img.shields.io/gitlab/pipeline/spkdev/money-open-exchange-rates/master)](https://gitlab.com/spkdev/money-open-exchange-rates/-/commits/master)
256
+ [![Coverage](https://gitlab.com/spkdev/money-open-exchange-rates/badges/master/coverage.svg)](https://gitlab.com/spkdev/money-open-exchange-rates/-/commits/master)
163
257
  [![Version](https://img.shields.io/gem/v/money-open-exchange-rates.svg)](https://rubygems.org/gems/money-open-exchange-rates)
164
258
  [![Documentation](https://img.shields.io/badge/doc-rubydoc-blue.svg)](http://www.rubydoc.info/gems/money-open-exchange-rates)
165
259
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT "MIT")
166
- [![Code Climate](https://img.shields.io/codeclimate/github/spk/money-open-exchange-rates.svg)](https://codeclimate.com/github/spk/money-open-exchange-rates)
167
260
  [![Inline docs](https://inch-ci.org/github/spk/money-open-exchange-rates.svg?branch=master)](http://inch-ci.org/github/spk/money-open-exchange-rates)
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'net/http'
3
4
  require 'uri'
4
- require 'open-uri'
5
5
  require 'money'
6
6
  require 'json'
7
- require File.expand_path('../../../open_exchange_rates_bank/version', __FILE__)
7
+ require File.expand_path('../../open_exchange_rates_bank/version', __dir__)
8
8
 
9
9
  # Money gem class
10
- # rubocop:disable ClassLength
10
+ # rubocop:disable Metrics/ClassLength
11
11
  class Money
12
12
  # https://github.com/RubyMoney/money#exchange-rate-stores
13
13
  module Bank
@@ -20,19 +20,16 @@ class Money
20
20
  # OpenExchangeRatesBank base class
21
21
  class OpenExchangeRatesBank < Money::Bank::VariableExchange
22
22
  VERSION = ::OpenExchangeRatesBank::VERSION
23
- BASE_URL = 'https://openexchangerates.org/api/'.freeze
23
+ BASE_URL = 'https://openexchangerates.org/api/'
24
24
 
25
25
  # OpenExchangeRates urls
26
26
  OER_URL = URI.join(BASE_URL, 'latest.json')
27
27
  OER_HISTORICAL_URL = URI.join(BASE_URL, 'historical/')
28
28
 
29
29
  # Default base currency "base": "USD"
30
- OE_SOURCE = 'USD'.freeze
31
-
32
- # @deprecated secure_connection is deprecated and has no effect
33
- def secure_connection=(*)
34
- 'secure_connection is deprecated and has no effect'
35
- end
30
+ OE_SOURCE = 'USD'
31
+ RATES_KEY = 'rates'
32
+ TIMESTAMP_KEY = 'timestamp'
36
33
 
37
34
  # As of the end of August 2012 all requests to the Open Exchange Rates
38
35
  # API must have a valid app_id
@@ -64,6 +61,13 @@ class Money
64
61
  # @return [String] The requested date in YYYY-MM-DD format
65
62
  attr_accessor :date
66
63
 
64
+ # Force refresh rates cache and store on the fly when ttl is expired
65
+ # This will slow down request on get_rate, so use at your on risk, if you
66
+ # don't want to setup crontab/worker/scheduler for your application
67
+ #
68
+ # @param [Boolean]
69
+ attr_accessor :force_refresh_rate_on_expire
70
+
67
71
  # Rates expiration Time
68
72
  #
69
73
  # @return [Time] expiration time
@@ -93,6 +97,38 @@ class Money
93
97
  # @return [Boolean] Setted show alternative
94
98
  attr_writer :show_alternative
95
99
 
100
+ # Filter response to a list of symbols
101
+ # see https://docs.openexchangerates.org/docs/get-specific-currencies
102
+ # @example
103
+ # oxr.symbols = [:usd, :cad]
104
+ #
105
+ # @param [Array] list of symbols
106
+ # @return [Array] Setted list of symbols
107
+ attr_writer :symbols
108
+
109
+ # Minified Response ('prettyprint')
110
+ # see https://docs.openexchangerates.org/docs/prettyprint
111
+ # @example
112
+ # oxr.prettyprint = false
113
+ #
114
+ # @param [Boolean] Set to false to receive minified (default: true)
115
+ # @return [Boolean]
116
+ attr_writer :prettyprint
117
+
118
+ # Set current rates timestamp
119
+ #
120
+ # @return [Time]
121
+ def rates_timestamp=(at)
122
+ @rates_timestamp = Time.at(at)
123
+ end
124
+
125
+ # Current rates timestamp
126
+ #
127
+ # @return [Time]
128
+ def rates_timestamp
129
+ @rates_timestamp || Time.now
130
+ end
131
+
96
132
  # Set the seconds after than the current rates are automatically expired
97
133
  # by default, they never expire.
98
134
  #
@@ -105,7 +141,7 @@ class Money
105
141
  def ttl_in_seconds=(value)
106
142
  @ttl_in_seconds = value
107
143
  refresh_rates_expiration if ttl_in_seconds
108
- @ttl_in_seconds
144
+ ttl_in_seconds
109
145
  end
110
146
 
111
147
  # Set the base currency for all rates. By default, USD is used.
@@ -119,9 +155,12 @@ class Money
119
155
  #
120
156
  # @return [String] chosen base currency
121
157
  def source=(value)
122
- @source = Money::Currency.find(value.to_s).iso_code
123
- rescue
124
- @source = OE_SOURCE
158
+ scurrency = Money::Currency.find(value.to_s)
159
+ @source = if scurrency
160
+ scurrency.iso_code
161
+ else
162
+ OE_SOURCE
163
+ end
125
164
  end
126
165
 
127
166
  # Get the base currency for all rates. By default, USD is used.
@@ -135,26 +174,19 @@ class Money
135
174
  #
136
175
  # @return [Array] Array of exchange rates
137
176
  def update_rates
138
- exchange_rates.each do |exchange_rate|
139
- rate = exchange_rate.last
140
- currency = exchange_rate.first
141
- next unless Money::Currency.find(currency)
142
- set_rate(source, currency, rate)
143
- set_rate(currency, source, 1.0 / rate)
177
+ store.transaction do
178
+ clear_rates!
179
+ exchange_rates.each do |exchange_rate|
180
+ rate = exchange_rate.last
181
+ currency = exchange_rate.first
182
+ next unless Money::Currency.find(currency)
183
+
184
+ set_rate(source, currency, rate)
185
+ set_rate(currency, source, 1.0 / rate)
186
+ end
144
187
  end
145
188
  end
146
189
 
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
190
  # Alias super method
159
191
  alias super_get_rate get_rate
160
192
 
@@ -171,13 +203,24 @@ class Money
171
203
  rate || calc_pair_rate_using_base(from_currency, to_currency, opts)
172
204
  end
173
205
 
206
+ # Fetch from url and save cache
207
+ #
208
+ # @return [Array] Array of exchange rates
209
+ def refresh_rates
210
+ read_from_url
211
+ end
212
+
213
+ # Alias refresh_rates method
214
+ alias save_rates refresh_rates
215
+
174
216
  # Expire rates when expired
175
217
  #
176
218
  # @return [NilClass, Time] nil if not expired or new expiration time
177
219
  def expire_rates
178
220
  return unless ttl_in_seconds
179
221
  return if rates_expiration > Time.now
180
- read_from_url
222
+
223
+ refresh_rates if force_refresh_rate_on_expire
181
224
  update_rates
182
225
  refresh_rates_expiration
183
226
  end
@@ -189,22 +232,48 @@ class Money
189
232
  @show_alternative ||= false
190
233
  end
191
234
 
235
+ # Get prettyprint option
236
+ #
237
+ # @return [Boolean]
238
+ def prettyprint
239
+ return true unless defined? @prettyprint
240
+ return true if @prettyprint.nil?
241
+
242
+ @prettyprint
243
+ end
244
+
245
+ # Get symbols
246
+ #
247
+ # @return [Array] list of symbols to filter by
248
+ def symbols
249
+ @symbols ||= nil
250
+ end
251
+
192
252
  # Source url of openexchangerates
193
253
  # defined with app_id
194
254
  #
195
255
  # @return [String] URL
196
256
  def source_url
197
- if source == OE_SOURCE
198
- "#{oer_url}?app_id=#{app_id}" \
199
- "&show_alternative=#{show_alternative}"
200
- else
201
- "#{oer_url}?app_id=#{app_id}&base=#{source}" \
202
- "&show_alternative=#{show_alternative}"
203
- end
257
+ str = "#{oer_url}?app_id=#{app_id}"
258
+ str = "#{str}&base=#{source}" unless source == OE_SOURCE
259
+ str = "#{str}&show_alternative=#{show_alternative}"
260
+ str = "#{str}&prettyprint=#{prettyprint}"
261
+ str = "#{str}&symbols=#{symbols.join(',')}" if symbols&.is_a?(Array)
262
+ str
204
263
  end
205
264
 
206
265
  protected
207
266
 
267
+ # Save rates on cache
268
+ # Can raise InvalidCache
269
+ #
270
+ # @return [Proc,File]
271
+ def save_cache
272
+ store_in_cache(@json_response) if valid_rates?(@json_response)
273
+ rescue Errno::ENOENT
274
+ raise InvalidCache
275
+ end
276
+
208
277
  # Latest url if no date given
209
278
  #
210
279
  # @return [String] URL
@@ -232,6 +301,7 @@ class Money
232
301
 
233
302
  # Store the provided text data by calling the proc method provided
234
303
  # for the cache, or write to the cache file.
304
+ # Can raise InvalidCache
235
305
  #
236
306
  # @example
237
307
  # oxr.store_in_cache("{\"rates\": {\"AED\": 3.67304}}")
@@ -241,10 +311,12 @@ class Money
241
311
  def store_in_cache(text)
242
312
  if cache.is_a?(Proc)
243
313
  cache.call(text)
244
- elsif cache.is_a?(String)
245
- open(cache, 'w') do |f|
314
+ elsif cache.is_a?(String) || cache.is_a?(Pathname)
315
+ File.open(cache.to_s, 'w') do |f|
246
316
  f.write(text)
247
317
  end
318
+ else
319
+ raise InvalidCache
248
320
  end
249
321
  end
250
322
 
@@ -254,19 +326,27 @@ class Money
254
326
  def read_from_cache
255
327
  result = if cache.is_a?(Proc)
256
328
  cache.call(nil)
257
- elsif cache.is_a?(String) && File.exist?(cache)
258
- open(cache).read
329
+ elsif File.exist?(cache.to_s)
330
+ File.read(cache)
259
331
  end
260
332
  result if valid_rates?(result)
261
333
  end
262
334
 
335
+ # Read API
336
+ #
337
+ # @return [String]
338
+ def api_response
339
+ Net::HTTP.get(URI(source_url))
340
+ end
341
+
263
342
  # Read from url
264
343
  #
265
344
  # @return [String] JSON content
266
345
  def read_from_url
267
346
  raise NoAppId if app_id.nil? || app_id.empty?
268
- @json_response = open(source_url).read
269
- save_rates
347
+
348
+ @json_response = api_response
349
+ save_cache if cache
270
350
  @json_response
271
351
  end
272
352
 
@@ -279,8 +359,9 @@ class Money
279
359
  # @return [Boolean] valid or not
280
360
  def valid_rates?(text)
281
361
  return false unless text
362
+
282
363
  parsed = JSON.parse(text)
283
- parsed && parsed.key?('rates')
364
+ parsed&.key?(RATES_KEY) && parsed&.key?(TIMESTAMP_KEY)
284
365
  rescue JSON::ParserError
285
366
  false
286
367
  end
@@ -290,14 +371,15 @@ class Money
290
371
  # @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
291
372
  def exchange_rates
292
373
  doc = JSON.parse(read_from_cache || read_from_url)
293
- @oer_rates = doc['rates']
374
+ self.rates_timestamp = doc[TIMESTAMP_KEY]
375
+ @oer_rates = doc[RATES_KEY]
294
376
  end
295
377
 
296
378
  # Refresh expiration from now
297
379
  #
298
380
  # @return [Time] new expiration time
299
381
  def refresh_rates_expiration
300
- @rates_expiration = Time.now + ttl_in_seconds
382
+ @rates_expiration = rates_timestamp + ttl_in_seconds
301
383
  end
302
384
 
303
385
  # Get rate or calculate it as inverse rate
@@ -328,13 +410,23 @@ class Money
328
410
  def calc_pair_rate_using_base(from_currency, to_currency, opts)
329
411
  from_base_rate = get_rate_or_calc_inverse(source, from_currency, opts)
330
412
  to_base_rate = get_rate_or_calc_inverse(source, to_currency, opts)
331
- if to_base_rate && from_base_rate
332
- rate = BigDecimal(to_base_rate.to_s) / from_base_rate
333
- add_rate(from_currency, to_currency, rate)
334
- return rate
413
+ return unless to_base_rate
414
+ return unless from_base_rate
415
+
416
+ rate = BigDecimal(to_base_rate.to_s) / from_base_rate
417
+ add_rate(from_currency, to_currency, rate)
418
+ rate
419
+ end
420
+
421
+ # Clears cached rates in store
422
+ #
423
+ # @return [Hash] All rates from store as Hash
424
+ def clear_rates!
425
+ store.each_rate do |iso_from, iso_to|
426
+ add_rate(iso_from, iso_to, nil)
335
427
  end
336
- nil
337
428
  end
338
429
  end
339
430
  end
340
431
  end
432
+ # rubocop:enable Metrics/ClassLength