money-currencylayer-bank 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2c5a08a77bda99cbfcd572fbd6aec7d24a30e4ea
4
- data.tar.gz: 17c697f0262bdedadf2c5bb9019364b10b3b8417
3
+ metadata.gz: 57c027e5e05e294bcaa1d9d8c62660a9b88dd164
4
+ data.tar.gz: 8ea8e257ea4fde758ab909d53955bba5d165a149
5
5
  SHA512:
6
- metadata.gz: 8afffad25d2b2fe4ae7253d903487253fb838b3a8fe260dda2f2160d80cc896102c4afe1654ca0436e0785ab90178832048088d66761d6c2af1c7a5c6dc8a475
7
- data.tar.gz: 033896568dfbe42df8e55042a7255390a2ed93a7257397e04cedd5d9357c2663f130cb5840193f24db752e706f3585e56276400b62574854bff7ee13d65907e2
6
+ metadata.gz: e67f41efcd22a9e3d388a8ab4c315e634ee9cc7e1b28344521dbbc52bdb65614fe05e28a6b538576ed7bac4c3433d39104d97c37482bc713d3b81513f368112a
7
+ data.tar.gz: 250b29148bf6119e1a22b8d58f9a9cd3d0b4b3e5812e65db7cfa1f8443482565edf786c290c6eb9c98c12a3183520814189b8543b3c8034855834767177fba01
data/README.md CHANGED
@@ -14,7 +14,7 @@ A gem that calculates the exchange rate using published rates from
14
14
  ~~~ ruby
15
15
  # Minimal requirements
16
16
  require 'money/bank/currencylayer_bank'
17
- mclb = Money::Bank::MoneyCurrencylayerBank.new
17
+ mclb = Money::Bank::CurrencylayerBank.new
18
18
  mclb.access_key = 'your access_key from https://currencylayer.com/product'
19
19
 
20
20
  # Update rates
@@ -53,7 +53,7 @@ You can also use it in Rails with the gem [money-rails](https://github.com/RubyM
53
53
  require 'money/bank/currencylayer_bank'
54
54
 
55
55
  MoneyRails.configure do |config|
56
- mclb = Money::Bank::MoneyCurrencylayerBank.new
56
+ mclb = Money::Bank::CurrencylayerBank.new
57
57
  mclb.access_key = 'your access_key from https://currencylayer.com/product'
58
58
  mclb.update_rates
59
59
 
@@ -14,11 +14,12 @@ class Money
14
14
  class NoAccessKey < StandardError; end
15
15
 
16
16
  # CurrencylayerBank base class
17
+ # rubocop:disable Metrics/ClassLength
17
18
  class CurrencylayerBank < Money::Bank::VariableExchange
18
19
  # CurrencylayerBank url
19
20
  CL_URL = 'http://apilayer.net/api/live'
20
21
  # CurrencylayerBank secure url
21
- SECURE_CL_URL = CL_URL.gsub('http:', 'https:')
22
+ CL_SECURE_URL = CL_URL.gsub('http:', 'https:')
22
23
  # Default base currency
23
24
  CL_SOURCE = 'USD'
24
25
 
@@ -37,7 +38,7 @@ class Money
37
38
  attr_reader :rates_expiration
38
39
 
39
40
  # Parsed CurrencylayerBank result as Hash
40
- attr_reader :cl_rates
41
+ attr_reader :rates
41
42
 
42
43
  # Seconds after than the current rates are automatically expired
43
44
  attr_reader :ttl_in_seconds
@@ -73,14 +74,14 @@ class Money
73
74
  # @return [Integer] Setted time to live in seconds
74
75
  def ttl_in_seconds=(value)
75
76
  @ttl_in_seconds = value
76
- refresh_rates_expiration if ttl_in_seconds
77
+ refresh_rates_expiration! if ttl_in_seconds
77
78
  @ttl_in_seconds
78
79
  end
79
80
 
80
81
  # Update all rates from CurrencylayerBank JSON
81
82
  # @return [Array] Array of exchange rates
82
- def update_rates
83
- exchange_rates.each do |exchange_rate|
83
+ def update_rates(straight = false)
84
+ exchange_rates(straight).each do |exchange_rate|
84
85
  currency = exchange_rate.first[3..-1]
85
86
  rate = exchange_rate.last
86
87
  next unless Money::Currency.find(currency)
@@ -89,34 +90,26 @@ class Money
89
90
  end
90
91
  end
91
92
 
92
- # Save rates on cache
93
- # Can raise InvalidCache
94
- #
95
- # @return [Proc,File]
96
- def save_rates
97
- fail InvalidCache unless cache
98
- text = read_from_url
99
- store_in_cache(text) if valid_rates?(text)
100
- rescue Errno::ENOENT
101
- raise InvalidCache
102
- end
103
-
104
93
  # Override Money `get_rate` method for caching
105
94
  # @param [String] from_currency Currency ISO code. ex. 'USD'
106
95
  # @param [String] to_currency Currency ISO code. ex. 'CAD'
107
96
  #
108
97
  # @return [Numeric] rate.
109
98
  def get_rate(from_currency, to_currency, opts = {})
110
- expire_rates
99
+ expire_rates!
111
100
  super
112
101
  end
113
102
 
114
103
  # Expire rates when expired
115
- def expire_rates
116
- return unless ttl_in_seconds
117
- return if rates_expiration > Time.now
118
- update_rates
119
- refresh_rates_expiration
104
+ def expire_rates!
105
+ return unless expired?
106
+ update_rates(true)
107
+ end
108
+
109
+ # Check if time is expired
110
+ # @return [Boolean] is the time expired.
111
+ def expired?
112
+ !ttl_in_seconds.nil? && rates_expiration < Time.now
120
113
  end
121
114
 
122
115
  # Source url of CurrencylayerBank
@@ -124,10 +117,17 @@ class Money
124
117
  def source_url
125
118
  fail NoAccessKey if access_key.nil? || access_key.empty?
126
119
  cl_url = CL_URL
127
- cl_url = SECURE_CL_URL if secure_connection
120
+ cl_url = CL_SECURE_URL if secure_connection
128
121
  "#{cl_url}?source=#{source}&access_key=#{access_key}"
129
122
  end
130
123
 
124
+ # Get the timestamp of rates
125
+ # @return [Time] Time object or nil
126
+ def rates_timestamp
127
+ parsed = raw_rates_careful
128
+ parsed.key?('timestamp') ? Time.at(parsed['timestamp']) : nil
129
+ end
130
+
131
131
  protected
132
132
 
133
133
  # Store the provided text data by calling the proc method provided
@@ -142,13 +142,23 @@ class Money
142
142
  if cache.is_a?(Proc)
143
143
  cache.call(text)
144
144
  elsif cache.is_a?(String)
145
- open(cache, 'w') do |f|
146
- f.write(text)
147
- end
145
+ write_to_file(text)
146
+ end
147
+ end
148
+
149
+ # Writes content to file cache
150
+ # @param text [String] String to cache
151
+ # @return [String,Integer]
152
+ def write_to_file(text)
153
+ open(cache, 'w') do |f|
154
+ f.write(text)
148
155
  end
156
+ rescue Errno::ENOENT
157
+ raise InvalidCache
149
158
  end
150
159
 
151
160
  # Read from cache when exist
161
+ # @return [Proc,String] JSON content
152
162
  def read_from_cache
153
163
  if cache.is_a?(Proc)
154
164
  cache.call(nil)
@@ -157,9 +167,20 @@ class Money
157
167
  end
158
168
  end
159
169
 
160
- # Read from url
170
+ # Get remote content and store in cache
161
171
  # @return [String] JSON content
162
172
  def read_from_url
173
+ text = open_url
174
+ if valid_rates?(text)
175
+ refresh_rates_expiration!
176
+ store_in_cache(text) if cache
177
+ end
178
+ text
179
+ end
180
+
181
+ # Opens an url and reads the content
182
+ # @return [String] JSON content
183
+ def open_url
163
184
  open(source_url).read
164
185
  end
165
186
 
@@ -177,25 +198,42 @@ class Money
177
198
  false
178
199
  end
179
200
 
180
- # Get expire rates, first from cache and then from url
201
+ # Get exchange rates with different strategies
202
+ #
203
+ # @example
204
+ # exchange_rates(true)
205
+ # exchange_rates
206
+ #
207
+ # @param [Boolean] true for straight, default is careful
181
208
  # @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
182
- def exchange_rates
183
- begin
184
- doc = JSON.parse(read_from_cache.to_s)
185
- rescue JSON::ParserError
186
- begin
187
- doc = JSON.parse(read_from_url)
188
- rescue JSON::ParserError
189
- doc = { 'quotes' => {} }
190
- end
209
+ def exchange_rates(straight = false)
210
+ if straight
211
+ @rates = raw_rates_straight['quotes']
212
+ else
213
+ @rates = raw_rates_careful['quotes']
191
214
  end
192
- @cl_rates = doc['quotes']
215
+ end
216
+
217
+ # Get raw exchange rates from cache and then from url
218
+ # @return [String] JSON content
219
+ def raw_rates_careful
220
+ JSON.parse(read_from_cache.to_s)
221
+ rescue JSON::ParserError
222
+ raw_rates_straight
223
+ end
224
+
225
+ # Get raw exchange rates from url
226
+ # @return [String] JSON content
227
+ def raw_rates_straight
228
+ JSON.parse(read_from_url)
229
+ rescue JSON::ParserError
230
+ { 'quotes' => {} }
193
231
  end
194
232
 
195
233
  # Refresh expiration from now
196
234
  # return [Time] new expiration time
197
- def refresh_rates_expiration
198
- @rates_expiration = Time.now + ttl_in_seconds
235
+ def refresh_rates_expiration!
236
+ @rates_expiration = Time.now + ttl_in_seconds unless ttl_in_seconds.nil?
199
237
  end
200
238
  end
201
239
  end
@@ -4,12 +4,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
4
4
  describe Money::Bank::CurrencylayerBank do
5
5
  subject { Money::Bank::CurrencylayerBank.new }
6
6
  let(:url) { Money::Bank::CurrencylayerBank::CL_URL }
7
- let(:secure_url) { Money::Bank::CurrencylayerBank::SECURE_CL_URL }
7
+ let(:secure_url) { Money::Bank::CurrencylayerBank::CL_SECURE_URL }
8
8
  let(:source) { Money::Bank::CurrencylayerBank::CL_SOURCE }
9
9
  let(:temp_cache_path) do
10
10
  File.expand_path(File.join(File.dirname(__FILE__), 'temp.json'))
11
11
  end
12
- let(:cache_path) do
12
+ let(:data_path) do
13
13
  File.expand_path(File.join(File.dirname(__FILE__), 'live.json'))
14
14
  end
15
15
 
@@ -17,8 +17,8 @@ describe Money::Bank::CurrencylayerBank do
17
17
  before do
18
18
  subject.access_key = TEST_ACCESS_KEY
19
19
  subject.cache = temp_cache_path
20
- stub(subject).read_from_url { File.read cache_path }
21
- subject.save_rates
20
+ stub(subject).source_url { data_path }
21
+ subject.update_rates
22
22
  end
23
23
 
24
24
  after do
@@ -61,61 +61,70 @@ describe Money::Bank::CurrencylayerBank do
61
61
  end
62
62
  end
63
63
 
64
- describe 'no cache' do
64
+ describe 'cache rates' do
65
65
  before do
66
- subject.cache = nil
67
66
  subject.access_key = TEST_ACCESS_KEY
68
- stub(subject).read_from_url { File.read cache_path }
67
+ subject.cache = temp_cache_path
68
+ stub(subject).source_url { data_path }
69
+ subject.update_rates
69
70
  end
70
71
 
71
- it 'should get from url' do
72
- subject.update_rates
73
- subject.cl_rates.wont_be_empty
72
+ after do
73
+ File.delete(temp_cache_path) if File.exist?(temp_cache_path)
74
74
  end
75
75
 
76
- it 'should raise an error if invalid path is given to save_rates' do
77
- proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
76
+ it 'should allow update after save' do
77
+ begin
78
+ subject.update_rates
79
+ rescue
80
+ assert false, 'Should allow updating after saving'
81
+ end
78
82
  end
79
- end
80
83
 
81
- describe '#secure_connection' do
82
- it "should use the non-secure http url if secure_connection isn't set" do
83
- subject.secure_connection = nil
84
- subject.access_key = TEST_ACCESS_KEY
85
- subject.source_url.must_equal "#{url}?source=#{source}&"\
86
- "access_key=#{TEST_ACCESS_KEY}"
84
+ it 'should not break an existing file if save fails to read' do
85
+ initial_size = File.read(temp_cache_path).size
86
+ stub(subject).open_url { '' }
87
+ subject.update_rates
88
+ File.read(temp_cache_path).size.must_equal initial_size
87
89
  end
88
90
 
89
- it 'should use the non-secure http url if secure_connection is false' do
90
- subject.secure_connection = false
91
- subject.access_key = TEST_ACCESS_KEY
92
- subject.source_url.must_equal "#{url}?source=#{source}&"\
93
- "access_key=#{TEST_ACCESS_KEY}"
91
+ it 'should not break an existing file if save returns json without rates' do
92
+ initial_size = File.read(temp_cache_path).size
93
+ stub(subject).open_url { '{ "error": "An error" }' }
94
+ subject.update_rates
95
+ File.read(temp_cache_path).size.must_equal initial_size
94
96
  end
95
97
 
96
- it 'should use the secure https url if secure_connection is set to true' do
97
- subject.secure_connection = true
98
- subject.access_key = TEST_ACCESS_KEY
99
- subject.source_url.must_equal "#{secure_url}?source=#{source}&"\
100
- "access_key=#{TEST_ACCESS_KEY}"
101
- subject.source_url.must_include 'https://'
98
+ it 'should not break an existing file if save returns a invalid json' do
99
+ initial_size = File.read(temp_cache_path).size
100
+ stub(subject).open_url { '{ invalid_json: "An error" }' }
101
+ subject.update_rates
102
+ File.read(temp_cache_path).size.must_equal initial_size
102
103
  end
103
104
  end
104
105
 
105
- describe 'no valid file for cache' do
106
+ describe 'no cache' do
106
107
  before do
107
- subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
108
+ subject.cache = nil
108
109
  subject.access_key = TEST_ACCESS_KEY
109
- stub(subject).read_from_url { File.read cache_path }
110
+ stub(subject).source_url { data_path }
110
111
  end
111
112
 
112
113
  it 'should get from url' do
113
114
  subject.update_rates
114
- subject.cl_rates.wont_be_empty
115
+ subject.rates.wont_be_empty
116
+ end
117
+ end
118
+
119
+ describe 'no valid file for cache' do
120
+ before do
121
+ subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
122
+ subject.access_key = TEST_ACCESS_KEY
123
+ stub(subject).source_url { data_path }
115
124
  end
116
125
 
117
- it 'should raise an error if invalid path is given to save_rates' do
118
- proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
126
+ it 'should raise an error if invalid path is given' do
127
+ proc { subject.update_rates }.must_raise Money::Bank::InvalidCache
119
128
  end
120
129
  end
121
130
 
@@ -133,31 +142,55 @@ describe Money::Bank::CurrencylayerBank do
133
142
  end
134
143
 
135
144
  it 'should get from url normally' do
136
- stub(subject).source_url { cache_path }
145
+ stub(subject).source_url { data_path }
137
146
  subject.update_rates
138
- subject.cl_rates.wont_be_empty
147
+ subject.rates.wont_be_empty
139
148
  end
140
149
 
141
150
  it 'should save from url and get from cache' do
142
- stub(subject).source_url { cache_path }
143
- subject.save_rates
151
+ stub(subject).source_url { data_path }
152
+ subject.update_rates
144
153
  @global_rates.wont_be_empty
145
154
  dont_allow(subject).source_url
146
155
  subject.update_rates
147
- subject.cl_rates.wont_be_empty
156
+ subject.rates.wont_be_empty
157
+ end
158
+ end
159
+
160
+ describe '#secure_connection' do
161
+ it "should use the non-secure http url if secure_connection isn't set" do
162
+ subject.secure_connection = nil
163
+ subject.access_key = TEST_ACCESS_KEY
164
+ subject.source_url.must_equal "#{url}?source=#{source}&"\
165
+ "access_key=#{TEST_ACCESS_KEY}"
166
+ end
167
+
168
+ it 'should use the non-secure http url if secure_connection is false' do
169
+ subject.secure_connection = false
170
+ subject.access_key = TEST_ACCESS_KEY
171
+ subject.source_url.must_equal "#{url}?source=#{source}&"\
172
+ "access_key=#{TEST_ACCESS_KEY}"
173
+ end
174
+
175
+ it 'should use the secure https url if secure_connection is set to true' do
176
+ subject.secure_connection = true
177
+ subject.access_key = TEST_ACCESS_KEY
178
+ subject.source_url.must_equal "#{secure_url}?source=#{source}&"\
179
+ "access_key=#{TEST_ACCESS_KEY}"
180
+ subject.source_url.must_include 'https://'
148
181
  end
149
182
  end
150
183
 
151
184
  describe '#update_rates' do
152
185
  before do
153
186
  subject.access_key = TEST_ACCESS_KEY
154
- subject.cache = cache_path
155
- stub(subject).read_from_url { File.read cache_path }
187
+ subject.cache = data_path
188
+ stub(subject).source_url { data_path }
156
189
  subject.update_rates
157
190
  end
158
191
 
159
192
  it 'should update itself with exchange rates from CurrencylayerBank' do
160
- subject.cl_rates.keys.each do |currency|
193
+ subject.rates.keys.each do |currency|
161
194
  next unless Money::Currency.find(currency)
162
195
  subject.get_rate('USD', currency).must_be :>, 0
163
196
  end
@@ -203,57 +236,15 @@ describe Money::Bank::CurrencylayerBank do
203
236
  describe '#access_key' do
204
237
  before do
205
238
  subject.cache = temp_cache_path
206
- stub(OpenURI::OpenRead).open(url) { File.read cache_path }
239
+ stub(OpenURI::OpenRead).open(url) { File.read data_path }
207
240
  end
208
241
 
209
242
  it 'should raise an error if no access key is set' do
210
- proc { subject.save_rates }.must_raise Money::Bank::NoAccessKey
243
+ proc { subject.update_rates }.must_raise Money::Bank::NoAccessKey
211
244
  end
212
245
  end
213
246
 
214
- describe '#save_rates' do
215
- before do
216
- subject.access_key = TEST_ACCESS_KEY
217
- subject.cache = temp_cache_path
218
- stub(subject).read_from_url { File.read cache_path }
219
- subject.save_rates
220
- end
221
-
222
- it 'should allow update after save' do
223
- begin
224
- subject.update_rates
225
- rescue
226
- assert false, 'Should allow updating after saving'
227
- end
228
- end
229
-
230
- it 'should not break an existing file if save fails to read' do
231
- initial_size = File.read(temp_cache_path).size
232
- stub(subject).read_from_url { '' }
233
- subject.save_rates
234
- File.open(temp_cache_path).read.size.must_equal initial_size
235
- end
236
-
237
- it 'should not break an existing file if save returns json without rates' do
238
- initial_size = File.read(temp_cache_path).size
239
- stub(subject).read_from_url { '{ "error": "An error" }' }
240
- subject.save_rates
241
- File.open(temp_cache_path).read.size.must_equal initial_size
242
- end
243
-
244
- it 'should not break an existing file if save returns a invalid json' do
245
- initial_size = File.read(temp_cache_path).size
246
- stub(subject).read_from_url { '{ invalid_json: "An error" }' }
247
- subject.save_rates
248
- File.open(temp_cache_path).read.size.must_equal initial_size
249
- end
250
-
251
- after do
252
- File.delete temp_cache_path
253
- end
254
- end
255
-
256
- describe '#expire_rates' do
247
+ describe '#expire_rates!' do
257
248
  before do
258
249
  subject.access_key = TEST_ACCESS_KEY
259
250
  subject.ttl_in_seconds = 1000
@@ -262,12 +253,11 @@ describe Money::Bank::CurrencylayerBank do
262
253
  @new_usd_eur_rate = 0.886584
263
254
  subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
264
255
  subject.cache = temp_cache_path
265
- stub(subject).read_from_url { File.read cache_path }
266
- subject.save_rates
256
+ stub(subject).source_url { data_path }
267
257
  end
268
258
 
269
259
  after do
270
- File.delete temp_cache_path
260
+ File.delete(temp_cache_path) if File.exist?(temp_cache_path)
271
261
  end
272
262
 
273
263
  describe 'when the ttl has expired' do
@@ -282,7 +272,7 @@ describe Money::Bank::CurrencylayerBank do
282
272
  it 'updates the next expiration time' do
283
273
  Timecop.freeze(Time.now + 1001) do
284
274
  exp_time = Time.now + 1000
285
- subject.expire_rates
275
+ subject.expire_rates!
286
276
  subject.rates_expiration.must_equal exp_time
287
277
  end
288
278
  end
@@ -290,10 +280,34 @@ describe Money::Bank::CurrencylayerBank do
290
280
 
291
281
  describe 'when the ttl has not expired' do
292
282
  it 'not should update the rates' do
283
+ subject.update_rates
293
284
  exp_time = subject.rates_expiration
294
- subject.expire_rates
285
+ subject.expire_rates!
295
286
  subject.rates_expiration.must_equal exp_time
296
287
  end
297
288
  end
298
289
  end
290
+
291
+ describe '#rates_timestamp' do
292
+ before do
293
+ subject.access_key = TEST_ACCESS_KEY
294
+ subject.cache = temp_cache_path
295
+ stub(subject).source_url { data_path }
296
+ end
297
+
298
+ after do
299
+ File.delete(temp_cache_path) if File.exist?(temp_cache_path)
300
+ end
301
+
302
+ it 'should return nil if no rates' do
303
+ stub(subject).open_url { '' }
304
+ subject.update_rates
305
+ subject.rates_timestamp.must_be_nil
306
+ end
307
+
308
+ it 'should return a Time object' do
309
+ subject.update_rates
310
+ subject.rates_timestamp.class.must_equal Time
311
+ end
312
+ end
299
313
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money-currencylayer-bank
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Egon Zemmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-28 00:00:00.000000000 Z
11
+ date: 2015-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: money