money-currencylayer-bank 0.4.1 → 0.4.2

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
  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