money-currencylayer-bank 0.4.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 37108e2aeabffc38f2c89a1db5161ca94d52e00e
4
+ data.tar.gz: 345f18d20d8cac0d4aa1a9b401021ec93af72301
5
+ SHA512:
6
+ metadata.gz: b10c6233f280ced125b6646341eac9b2e9859581154f8643d1ac984f44ad2018f1a2f53869d0e28d8b8be0fd6758bdc7680fda8880f683c51a35779c8a23e1d8
7
+ data.tar.gz: 334f01cd4f112a59adad4596b5f105dcaf2bf068c3077abd4f8d16936e6f9c25beed962cbff4f44a3ca654166fe4f43466e3786de95602763c432cc07659ea44
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Phlegx Systems OG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Money Currencylayer Bank
2
+
3
+ A gem that calculates the exchange rate using published rates from
4
+ [currencylayer.com](https://currencylayer.com/)
5
+
6
+ ## Usage
7
+
8
+ ~~~ ruby
9
+ # Minimal requirements
10
+ require 'money/bank/currencylayer_bank'
11
+ mclb = Money::Bank::MoneyCurrencylayerBank.new
12
+ mclb.access_key = 'your access_key from https://currencylayer.com/product'
13
+
14
+ # Update rates
15
+ mclb.update_rates
16
+
17
+ # Store in cache
18
+ mclb.save_rates
19
+
20
+ # (optional)
21
+ # Set the base currency for all rates. By default, USD is used.
22
+ # CurrencylayerBank only allows USD as base currency for the free plan users.
23
+ mclb.source = 'EUR'
24
+
25
+ # (optional)
26
+ # Set the seconds after than the current rates are automatically expired
27
+ # by default, they never expire, in this example 1 day.
28
+ mclb.ttl_in_seconds = 86400
29
+
30
+ # (optional)
31
+ # Use https to fetch rates from CurrencylayerBank
32
+ # CurrencylayerBank only allows http as connection for the free plan users.
33
+ mclb.secure_connection = true
34
+
35
+ # Define cache
36
+ mclb.cache = 'path/to/file/cache'
37
+
38
+ # Set money default bank to currencylayer bank
39
+ Money.default_bank = mclb
40
+ ~~~
41
+
42
+ ### Using gem money-rails
43
+
44
+ You can also use it in Rails with the gem [money-rails](https://github.com/RubyMoney/money-rails).
45
+
46
+ ~~~ ruby
47
+ require 'money/bank/currencylayer_bank'
48
+
49
+ MoneyRails.configure do |config|
50
+ mclb = Money::Bank::MoneyCurrencylayerBank.new
51
+ mclb.access_key = 'your access_key from https://currencylayer.com/product'
52
+ mclb.update_rates
53
+
54
+ config.default_bank = mclb
55
+ end
56
+ ~~~
57
+
58
+ ### Cache
59
+
60
+ You can also provide a Proc as a cache to provide your own caching mechanism
61
+ perhaps with Redis or just a thread safe `Hash` (global). For example:
62
+
63
+ ~~~ ruby
64
+ mclb.cache = Proc.new do |v|
65
+ key = 'money:currencylayer_bank'
66
+ if v
67
+ Thread.current[key] = v
68
+ else
69
+ Thread.current[key]
70
+ end
71
+ end
72
+ ~~~
73
+
74
+ ## Process
75
+
76
+ The gem fetches all rates in a cache with USD as base currency. It's possible to compute the rate between any of the currencies by calculating a pair rate using base USD rate.
77
+
78
+ ## Tests
79
+
80
+ You can place your own key on a file or environment
81
+ variable named TEST_ACCESS_KEY and then run:
82
+
83
+ ~~~
84
+ bundle exec rake
85
+ ~~~
86
+
87
+ ## Refs
88
+
89
+ * <https://github.com/RubyMoney/money>
90
+ * <https://github.com/currencybot/open-exchange-rates>
91
+
92
+ ## Contributors
93
+
94
+ * See [github.com/phlegx/money-currencylayer-bank](https://github.com/phlegx/money-currencylayer-bank/graphs/contributors).
95
+ * Inspired by [github.com/spk/money-open-exchange-rates](https://github.com/spk/money-open-exchange-rates/graphs/contributors).
96
+
97
+ ## License
98
+
99
+ The MIT License
100
+
101
+ Copyright (c) 2015 Phlegx Systems OG
102
+
103
+ ---
104
+ [![Gem Version](https://badge.fury.io/rb/money-currencylayer-bank.svg)](https://rubygems.org/gems/money-currencylayer-bank)
105
+ [![Build Status](https://secure.travis-ci.org/phlegx/money-currencylayer-bank.svg?branch=master)](https://travis-ci.org/phlegx/money-currencylayer-bank)
106
+ [![Code Climate](http://img.shields.io/codeclimate/github/phlegx/money-currencylayer-bank.svg)](https://codeclimate.com/github/phlegx/money-currencylayer-bank)
107
+ [![Inline docs](http://inch-ci.org/github/phlegx/money-currencylayer-bank.svg?branch=master)](http://inch-ci.org/github/phlegx/money-currencylayer-bank)
108
+ [![License](https://img.shields.io/github/license/phlegx/money-currencylayer-bank.svg)](http://opensource.org/licenses/MIT)
@@ -0,0 +1,192 @@
1
+ # encoding: UTF-8
2
+ require 'open-uri'
3
+ require 'money'
4
+ require 'json'
5
+
6
+ # Money gem class
7
+ class Money
8
+ # https://github.com/RubyMoney/money#exchange-rate-stores
9
+ module Bank
10
+ # Invalid cache, file not found or cache empty
11
+ class InvalidCache < StandardError; end
12
+
13
+ # App id not set error
14
+ class NoAccessKey < StandardError; end
15
+
16
+ # CurrencylayerBank base class
17
+ class CurrencylayerBank < Money::Bank::VariableExchange
18
+ # CurrencylayerBank url
19
+ CL_URL = 'http://apilayer.net/api/live'
20
+ # CurrencylayerBank secure url
21
+ SECURE_CL_URL = CL_URL.gsub('http:', 'https:')
22
+ # Default base currency
23
+ CL_SOURCE = 'USD'
24
+
25
+ # Use https to fetch rates from CurrencylayerBank
26
+ # CurrencylayerBank only allows http as connection for the free plan users.
27
+ attr_accessor :secure_connection
28
+
29
+ # API must have a valid access_key
30
+ attr_accessor :access_key
31
+
32
+ # Cache accessor, can be a String or a Proc
33
+ attr_accessor :cache
34
+
35
+ # Rates expiration Time
36
+ attr_reader :rates_expiration
37
+
38
+ # Parsed CurrencylayerBank result as Hash
39
+ attr_reader :cl_rates
40
+
41
+ # Seconds after than the current rates are automatically expired
42
+ attr_reader :ttl_in_seconds
43
+
44
+ # Set the base currency for all rates. By default, USD is used.
45
+ # CurrencylayerBank only allows USD as base currency for the free plan users.
46
+ #
47
+ # @example
48
+ # source = 'USD'
49
+ #
50
+ # @param value [String] Currency code, ISO 3166-1 alpha-3
51
+ #
52
+ # @return [String] Setted base currency
53
+ def source=(value)
54
+ @source = Money::Currency.find(value.to_s).try(:iso_code) || CL_SOURCE
55
+ end
56
+
57
+ # Get the base currency for all rates. By default, USD is used.
58
+ # @return [String] Base currency
59
+ def source
60
+ @source ||= CL_SOURCE
61
+ end
62
+
63
+ # Set the seconds after than the current rates are automatically expired
64
+ # by default, they never expire.
65
+ #
66
+ # @example
67
+ # ttl_in_seconds = 86400 # will expire the rates in one day
68
+ #
69
+ # @param value [Integer] Time to live in seconds
70
+ #
71
+ # @return [Integer] Setted time to live in seconds
72
+ def ttl_in_seconds=(value)
73
+ @ttl_in_seconds = value
74
+ refresh_rates_expiration if ttl_in_seconds
75
+ @ttl_in_seconds
76
+ end
77
+
78
+ # Update all rates from CurrencylayerBank JSON
79
+ # @return [Array] Array of exchange rates
80
+ def update_rates
81
+ exchange_rates.each do |exchange_rate|
82
+ currency = exchange_rate.first[3..-1]
83
+ rate = exchange_rate.last
84
+ next unless Money::Currency.find(currency)
85
+ set_rate(source, currency, rate)
86
+ set_rate(currency, source, 1.0 / rate)
87
+ end
88
+ end
89
+
90
+ # Save rates on cache
91
+ # Can raise InvalidCache
92
+ #
93
+ # @return [Proc,File]
94
+ def save_rates
95
+ fail InvalidCache unless cache
96
+ text = read_from_url
97
+ store_in_cache(text) if valid_rates?(text)
98
+ rescue Errno::ENOENT
99
+ raise InvalidCache
100
+ end
101
+
102
+ # Override Money `get_rate` method for caching
103
+ # @param [String] from_currency Currency ISO code. ex. 'USD'
104
+ # @param [String] to_currency Currency ISO code. ex. 'CAD'
105
+ #
106
+ # @return [Numeric] rate.
107
+ def get_rate(from_currency, to_currency, opts = {})
108
+ expire_rates
109
+ super
110
+ end
111
+
112
+ # Expire rates when expired
113
+ def expire_rates
114
+ return unless ttl_in_seconds
115
+ return if rates_expiration > Time.now
116
+ update_rates
117
+ refresh_rates_expiration
118
+ end
119
+
120
+ # Source url of CurrencylayerBank
121
+ # defined with access_key and secure_connection
122
+ def source_url
123
+ fail NoAccessKey if access_key.nil? || access_key.empty?
124
+ cl_url = CL_URL
125
+ cl_url = SECURE_CL_URL if secure_connection
126
+ "#{cl_url}?source=#{source}&access_key=#{access_key}"
127
+ end
128
+
129
+ protected
130
+
131
+ # Store the provided text data by calling the proc method provided
132
+ # for the cache, or write to the cache file.
133
+ #
134
+ # @example
135
+ # store_in_cache("{\"quotes\": {\"USDAED\": 3.67304}}")
136
+ #
137
+ # @param text [String] String to cache
138
+ # @return [String,Integer]
139
+ def store_in_cache(text)
140
+ if cache.is_a?(Proc)
141
+ cache.call(text)
142
+ elsif cache.is_a?(String)
143
+ open(cache, 'w') do |f|
144
+ f.write(text)
145
+ end
146
+ end
147
+ end
148
+
149
+ # Read from cache when exist
150
+ def read_from_cache
151
+ if cache.is_a?(Proc)
152
+ cache.call(nil)
153
+ elsif cache.is_a?(String) && File.exist?(cache)
154
+ open(cache).read
155
+ end
156
+ end
157
+
158
+ # Read from url
159
+ # @return [String] JSON content
160
+ def read_from_url
161
+ open(source_url).read
162
+ end
163
+
164
+ # Check validity of rates response only for store in cache
165
+ #
166
+ # @example
167
+ # valid_rates?("{\"quotes\": {\"USDAED\": 3.67304}}")
168
+ #
169
+ # @param [String] text is JSON content
170
+ # @return [Boolean] valid or not
171
+ def valid_rates?(text)
172
+ parsed = JSON.parse(text)
173
+ parsed && parsed.key?('quotes')
174
+ rescue JSON::ParserError
175
+ false
176
+ end
177
+
178
+ # Get expire rates, first from cache and then from url
179
+ # @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
180
+ def exchange_rates
181
+ doc = JSON.parse(read_from_cache) rescue doc = JSON.parse(read_from_url) rescue doc = { 'quotes' => {}}
182
+ @cl_rates = doc['quotes']
183
+ end
184
+
185
+ # Refresh expiration from now
186
+ # return [Time] new expiration time
187
+ def refresh_rates_expiration
188
+ @rates_expiration = Time.now + ttl_in_seconds
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,295 @@
1
+ # encoding: UTF-8
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
3
+
4
+ describe Money::Bank::CurrencylayerBank do
5
+ subject { Money::Bank::CurrencylayerBank.new }
6
+ let(:url) { Money::Bank::CurrencylayerBank::CL_URL }
7
+ let(:secure_url) { Money::Bank::CurrencylayerBank::SECURE_CL_URL }
8
+ let(:source) { Money::Bank::CurrencylayerBank::CL_SOURCE }
9
+ let(:temp_cache_path) do
10
+ File.expand_path(File.join(File.dirname(__FILE__), 'temp.json'))
11
+ end
12
+ let(:cache_path) do
13
+ File.expand_path(File.join(File.dirname(__FILE__), 'live.json'))
14
+ end
15
+
16
+ describe 'exchange' do
17
+ before do
18
+ subject.access_key = TEST_ACCESS_KEY
19
+ subject.cache = temp_cache_path
20
+ stub(subject).read_from_url { File.read cache_path }
21
+ subject.save_rates
22
+ end
23
+
24
+ after do
25
+ File.unlink(temp_cache_path)
26
+ end
27
+
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
32
+ end
33
+
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
38
+ end
39
+ end
40
+
41
+ describe 'with rates' do
42
+ before do
43
+ subject.update_rates
44
+ end
45
+
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')
49
+ end
50
+
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')
54
+ end
55
+
56
+ it "should raise if it can't find an exchange rate" do
57
+ money = Money.new(0, 'USD')
58
+ proc { subject.exchange_with(money, 'SSP') }
59
+ .must_raise Money::Bank::UnknownRate
60
+ end
61
+ end
62
+ end
63
+
64
+ describe 'no cache' do
65
+ before do
66
+ subject.cache = nil
67
+ subject.access_key = TEST_ACCESS_KEY
68
+ end
69
+
70
+ it 'should get from url' do
71
+ stub(OpenURI::OpenRead).open(url) { File.read cache_path }
72
+ subject.update_rates
73
+ subject.cl_rates.wont_be_empty
74
+ end
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
78
+ end
79
+ end
80
+
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}&access_key=#{TEST_ACCESS_KEY}"
86
+ end
87
+
88
+ it 'should use the non-secure http url if secure_connection is false' do
89
+ subject.secure_connection = false
90
+ subject.access_key = TEST_ACCESS_KEY
91
+ subject.source_url.must_equal "#{url}?source=#{source}&access_key=#{TEST_ACCESS_KEY}"
92
+ end
93
+
94
+ it 'should use the secure https url if secure_connection is set to true' do
95
+ subject.secure_connection = true
96
+ subject.access_key = TEST_ACCESS_KEY
97
+ subject.source_url.must_equal "#{secure_url}?source=#{source}&access_key=#{TEST_ACCESS_KEY}"
98
+ subject.source_url.must_include 'https://'
99
+ end
100
+ end
101
+
102
+ describe 'no valid file for cache' do
103
+ before do
104
+ subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
105
+ subject.access_key = TEST_ACCESS_KEY
106
+ end
107
+
108
+ it 'should get from url' do
109
+ stub(OpenURI::OpenRead).open(url) { File.read cache_path }
110
+ subject.update_rates
111
+ subject.cl_rates.wont_be_empty
112
+ end
113
+
114
+ it 'should raise an error if invalid path is given to save_rates' do
115
+ proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
116
+ end
117
+ end
118
+
119
+ describe 'using proc for cache' do
120
+ before :each do
121
+ @global_rates = nil
122
+ subject.cache = proc { |v|
123
+ if v
124
+ @global_rates = v
125
+ else
126
+ @global_rates
127
+ end
128
+ }
129
+ subject.access_key = TEST_ACCESS_KEY
130
+ end
131
+
132
+ it 'should get from url normally' do
133
+ stub(subject).source_url { cache_path }
134
+ subject.update_rates
135
+ subject.cl_rates.wont_be_empty
136
+ end
137
+
138
+ it 'should save from url and get from cache' do
139
+ stub(subject).source_url { cache_path }
140
+ subject.save_rates
141
+ @global_rates.wont_be_empty
142
+ dont_allow(subject).source_url
143
+ subject.update_rates
144
+ subject.cl_rates.wont_be_empty
145
+ end
146
+ end
147
+
148
+ describe '#update_rates' do
149
+ before do
150
+ subject.access_key = TEST_ACCESS_KEY
151
+ subject.cache = cache_path
152
+ subject.update_rates
153
+ end
154
+
155
+ it 'should update itself with exchange rates from CurrencylayerBank' do
156
+ subject.cl_rates.keys.each do |currency|
157
+ next unless Money::Currency.find(currency)
158
+ subject.get_rate('USD', currency).must_be :>, 0
159
+ end
160
+ end
161
+
162
+ it 'should not return 0 with integer rate' do
163
+ wtf = {
164
+ priority: 1,
165
+ iso_code: 'WTF',
166
+ name: 'WTF',
167
+ symbol: 'WTF',
168
+ subunit: 'Cent',
169
+ subunit_to_unit: 1000,
170
+ separator: '.',
171
+ delimiter: ','
172
+ }
173
+ Money::Currency.register(wtf)
174
+ subject.add_rate('USD', 'WTF', 2)
175
+ subject.add_rate('WTF', 'USD', 2)
176
+ subject.exchange_with(5000.to_money('WTF'), 'USD').cents.wont_equal 0
177
+ end
178
+
179
+ # in response to #4
180
+ it 'should exchange btc' do
181
+ btc = {
182
+ priority: 1,
183
+ iso_code: 'BTC',
184
+ name: 'Bitcoin',
185
+ symbol: 'BTC',
186
+ subunit: 'Cent',
187
+ subunit_to_unit: 1000,
188
+ separator: '.',
189
+ delimiter: ','
190
+ }
191
+ Money::Currency.register(btc)
192
+ rate = 13.7603
193
+ subject.add_rate('USD', 'BTC', 1 / 13.7603)
194
+ subject.add_rate('BTC', 'USD', rate)
195
+ subject.exchange_with(100.to_money('BTC'), 'USD').cents.must_equal 137_603
196
+ end
197
+ end
198
+
199
+ describe '#access_key' do
200
+ before do
201
+ subject.cache = temp_cache_path
202
+ stub(OpenURI::OpenRead).open(url) { File.read cache_path }
203
+ end
204
+
205
+ it 'should raise an error if no access key is set' do
206
+ proc { subject.save_rates }.must_raise Money::Bank::NoAccessKey
207
+ end
208
+ end
209
+
210
+ describe '#save_rates' do
211
+ before do
212
+ subject.access_key = TEST_ACCESS_KEY
213
+ subject.cache = temp_cache_path
214
+ stub(OpenURI::OpenRead).open(url) { File.read cache_path }
215
+ subject.save_rates
216
+ end
217
+
218
+ it 'should allow update after save' do
219
+ begin
220
+ subject.update_rates
221
+ rescue
222
+ assert false, 'Should allow updating after saving'
223
+ end
224
+ end
225
+
226
+ it 'should not break an existing file if save fails to read' do
227
+ initial_size = File.read(temp_cache_path).size
228
+ stub(subject).read_from_url { '' }
229
+ subject.save_rates
230
+ File.open(temp_cache_path).read.size.must_equal initial_size
231
+ end
232
+
233
+ it 'should not break an existing file if save returns json without rates' do
234
+ initial_size = File.read(temp_cache_path).size
235
+ stub(subject).read_from_url { '{ "error": "An error" }' }
236
+ subject.save_rates
237
+ File.open(temp_cache_path).read.size.must_equal initial_size
238
+ end
239
+
240
+ it 'should not break an existing file if save returns a invalid json' do
241
+ initial_size = File.read(temp_cache_path).size
242
+ stub(subject).read_from_url { '{ invalid_json: "An error" }' }
243
+ subject.save_rates
244
+ File.open(temp_cache_path).read.size.must_equal initial_size
245
+ end
246
+
247
+ after do
248
+ File.delete temp_cache_path
249
+ end
250
+ end
251
+
252
+ describe '#expire_rates' do
253
+ before do
254
+ subject.access_key = TEST_ACCESS_KEY
255
+ subject.ttl_in_seconds = 1000
256
+ @old_usd_eur_rate = 0.655
257
+ # see test/latest.json +54
258
+ @new_usd_eur_rate = 0.886584
259
+ subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
260
+ subject.cache = temp_cache_path
261
+ stub(subject).read_from_url { File.read cache_path }
262
+ subject.save_rates
263
+ end
264
+
265
+ after do
266
+ File.delete temp_cache_path
267
+ end
268
+
269
+ describe 'when the ttl has expired' do
270
+ it 'should update the rates' do
271
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
272
+ Timecop.freeze(Time.now + 1001) do
273
+ subject.get_rate('USD', 'EUR').wont_equal @old_usd_eur_rate
274
+ subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
275
+ end
276
+ end
277
+
278
+ it 'updates the next expiration time' do
279
+ Timecop.freeze(Time.now + 1001) do
280
+ exp_time = Time.now + 1000
281
+ subject.expire_rates
282
+ subject.rates_expiration.must_equal exp_time
283
+ end
284
+ end
285
+ end
286
+
287
+ describe 'when the ttl has not expired' do
288
+ it 'not should update the rates' do
289
+ exp_time = subject.rates_expiration
290
+ subject.expire_rates
291
+ subject.rates_expiration.must_equal exp_time
292
+ end
293
+ end
294
+ end
295
+ end
data/test/live.json ADDED
@@ -0,0 +1,177 @@
1
+ {
2
+ "success":true,
3
+ "terms":"https:\/\/currencylayer.com\/terms",
4
+ "privacy":"https:\/\/currencylayer.com\/privacy",
5
+ "timestamp":1440755169,
6
+ "source":"USD",
7
+ "quotes":{
8
+ "USDAED":3.67315,
9
+ "USDAFN":65.059998,
10
+ "USDALL":124.204498,
11
+ "USDAMD":484.700012,
12
+ "USDANG":1.789762,
13
+ "USDAOA":125.800003,
14
+ "USDARS":9.289801,
15
+ "USDAUD":1.396775,
16
+ "USDAWG":1.79,
17
+ "USDAZN":1.05025,
18
+ "USDBAM":1.73215,
19
+ "USDBBD":2,
20
+ "USDBDT":78.16745,
21
+ "USDBGN":1.733895,
22
+ "USDBHD":0.37743,
23
+ "USDBIF":1558.050049,
24
+ "USDBMD":1,
25
+ "USDBND":1.403802,
26
+ "USDBOB":6.901852,
27
+ "USDBRL":3.55925,
28
+ "USDBSD":1,
29
+ "USDBTC":0.004416,
30
+ "USDBTN":66.165001,
31
+ "USDBWP":10.26445,
32
+ "USDBYR":17500,
33
+ "USDBZD":1.994978,
34
+ "USDCAD":1.321355,
35
+ "USDCDF":927.999825,
36
+ "USDCHF":0.962099,
37
+ "USDCLF":0.0246,
38
+ "USDCLP":694.494995,
39
+ "USDCNY":6.388203,
40
+ "USDCOP":3171.030029,
41
+ "USDCRC":532.380005,
42
+ "USDCUC":1,
43
+ "USDCUP":0.999558,
44
+ "USDCVE":98.070999,
45
+ "USDCZK":23.979502,
46
+ "USDDJF":177.669998,
47
+ "USDDKK":6.616496,
48
+ "USDDOP":44.889999,
49
+ "USDDZD":105.955002,
50
+ "USDEEK":13.850996,
51
+ "USDEGP":7.827797,
52
+ "USDERN":15.280041,
53
+ "USDETB":20.8335,
54
+ "USDEUR":0.886584,
55
+ "USDFJD":2.15655,
56
+ "USDFKP":0.648498,
57
+ "USDGBP":0.649287,
58
+ "USDGEL":2.344962,
59
+ "USDGGP":0.64902,
60
+ "USDGHS":4.117502,
61
+ "USDGIP":0.6485,
62
+ "USDGMD":39.630001,
63
+ "USDGNF":7277.700195,
64
+ "USDGTQ":7.672497,
65
+ "USDGYD":207.210007,
66
+ "USDHKD":7.75022,
67
+ "USDHNL":21.988899,
68
+ "USDHRK":6.7026,
69
+ "USDHTG":51.446201,
70
+ "USDHUF":279.019989,
71
+ "USDIDR":13995.5,
72
+ "USDILS":3.92785,
73
+ "USDIMP":0.64909,
74
+ "USDINR":66.160454,
75
+ "USDIQD":1149.349976,
76
+ "USDIRR":29929.999905,
77
+ "USDISK":129.264999,
78
+ "USDJEP":0.64908,
79
+ "USDJMD":117.410004,
80
+ "USDJOD":0.70905,
81
+ "USDJPY":120.879501,
82
+ "USDKES":103.844498,
83
+ "USDKGS":65.995598,
84
+ "USDKHR":4116.250384,
85
+ "USDKMF":435.697449,
86
+ "USDKPW":900.000329,
87
+ "USDKRW":1176.954956,
88
+ "USDKWD":0.30221,
89
+ "USDKYD":0.819677,
90
+ "USDKZT":241.599945,
91
+ "USDLAK":8196.599609,
92
+ "USDLBP":1506.501675,
93
+ "USDLKR":134.779999,
94
+ "USDLRD":84.660004,
95
+ "USDLSL":13.188027,
96
+ "USDLTL":3.048701,
97
+ "USDLVL":0.62055,
98
+ "USDLYD":1.37265,
99
+ "USDMAD":9.67355,
100
+ "USDMDL":19.049999,
101
+ "USDMGA":3322.699951,
102
+ "USDMKD":54.830002,
103
+ "USDMMK":1281.750421,
104
+ "USDMNT":1991.000101,
105
+ "USDMOP":7.98255,
106
+ "USDMRO":312.000071,
107
+ "USDMUR":35.150002,
108
+ "USDMVR":15.359568,
109
+ "USDMWK":555.734985,
110
+ "USDMXN":16.911249,
111
+ "USDMYR":4.187203,
112
+ "USDMZN":41.255001,
113
+ "USDNAD":13.18801,
114
+ "USDNGN":199.050003,
115
+ "USDNIO":27.4655,
116
+ "USDNOK":8.284018,
117
+ "USDNPR":105.863998,
118
+ "USDNZD":1.545237,
119
+ "USDOMR":0.38515,
120
+ "USDPAB":1,
121
+ "USDPEN":3.281097,
122
+ "USDPGK":2.8063,
123
+ "USDPHP":46.721501,
124
+ "USDPKR":103.904999,
125
+ "USDPLN":3.75175,
126
+ "USDPYG":5393.502337,
127
+ "USDQAR":3.64245,
128
+ "USDRON":3.92755,
129
+ "USDRSD":106.370003,
130
+ "USDRUB":66.537804,
131
+ "USDRWF":729.349976,
132
+ "USDSAR":3.75055,
133
+ "USDSBD":7.968634,
134
+ "USDSCR":13.03295,
135
+ "USDSDG":6.076982,
136
+ "USDSEK":8.44225,
137
+ "USDSGD":1.404397,
138
+ "USDSHP":0.6485,
139
+ "USDSLL":4638.000576,
140
+ "USDSOS":661.000153,
141
+ "USDSRD":3.296279,
142
+ "USDSTD":21789.5,
143
+ "USDSVC":8.734966,
144
+ "USDSYP":188.822006,
145
+ "USDSZL":13.187971,
146
+ "USDTHB":35.869999,
147
+ "USDTJS":6.319802,
148
+ "USDTMT":3.5,
149
+ "USDTND":1.95735,
150
+ "USDTOP":2.127081,
151
+ "USDTRY":2.91595,
152
+ "USDTTD":6.348697,
153
+ "USDTWD":32.221001,
154
+ "USDTZS":2153.300049,
155
+ "USDUAH":21.149933,
156
+ "USDUGX":3630.00047,
157
+ "USDUSD":1,
158
+ "USDUYU":28.52497,
159
+ "USDUZS":2595.520019,
160
+ "USDVEF":6.349652,
161
+ "USDVND":22462.5,
162
+ "USDVUV":111.910004,
163
+ "USDWST":2.620836,
164
+ "USDXAF":580.929871,
165
+ "USDXAG":0.069132,
166
+ "USDXAU":0.000887,
167
+ "USDXCD":2.700532,
168
+ "USDXDR":0.70995,
169
+ "USDXOF":580.929871,
170
+ "USDXPF":105.682747,
171
+ "USDYER":214.889999,
172
+ "USDZAR":13.17765,
173
+ "USDZMK":5156.098401,
174
+ "USDZMW":8.630369,
175
+ "USDZWL":322.355011
176
+ }
177
+ }
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+ require 'minitest/autorun'
3
+ require 'rr'
4
+ require 'money/bank/currencylayer_bank'
5
+ require 'monetize'
6
+ require 'timecop'
7
+ require 'pry'
8
+
9
+ TEST_ACCESS_KEY_PATH = File.join(File.dirname(__FILE__), '..', 'TEST_ACCESS_KEY')
10
+ TEST_ACCESS_KEY = ENV['TEST_ACCESS_KEY'] || File.read(TEST_ACCESS_KEY_PATH)
11
+
12
+ if TEST_ACCESS_KEY.nil? || TEST_ACCESS_KEY.empty?
13
+ fail "Please add a valid access key to file #{TEST_ACCESS_KEY_PATH} or to " \
14
+ ' TEST_TEST_ACCESS_KEY environment'
15
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: money-currencylayer-bank
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Egon Zemmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: money
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: monetize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-line
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
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'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: inch
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: A gem that calculates the exchange rate using published rates from currencylayer.com.
168
+ Compatible with the money gem.
169
+ email: office@phlegx.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files:
173
+ - README.md
174
+ files:
175
+ - Gemfile
176
+ - LICENSE
177
+ - README.md
178
+ - lib/money/bank/currencylayer_bank.rb
179
+ - test/currencylayer_bank_test.rb
180
+ - test/live.json
181
+ - test/test_helper.rb
182
+ homepage: http://github.com/phlegx/money-currencylayer-bank
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: 1.9.3
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.4.8
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: A gem that calculates the exchange rate using published rates from currencylayer.com.
206
+ test_files:
207
+ - test/currencylayer_bank_test.rb
208
+ has_rdoc: