currencylayer-historical-bank 0.0.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 +7 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/lib/money/bank/currencylayer_historical_bank.rb +301 -0
- data/lib/money/version.rb +0 -0
- data/test/TEST_ACCESS_KEY +1 -0
- data/test/currencylayer_bank_test.rb +296 -0
- data/test/live.json +177 -0
- data/test/test_helper.rb +15 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f13d512a100631703bce783678d23f2ed8f49bde
|
4
|
+
data.tar.gz: bad37b4cad23a26ade8b03554b4578bffa081ca4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b5b419e7153236ff6d600b8f5061414616245ffd568ecb4bb4c6a5a093135938ab015600af956e856073dbd63ae292727c52ef3d664d0cb28b4a64fc5db7b748
|
7
|
+
data.tar.gz: 314d2525d4a57fd6c377af83703abbadf44245c6b81e5566f5238e8fb1bb95464891fc035c3601c4cbf419a3caac6c403649dbef9890d58c9efe6a3526d9d250
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017
|
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,185 @@
|
|
1
|
+
A gem that calculates the exchange rate using published rates from
|
2
|
+
[currencylayer.com](https://currencylayer.com/)
|
3
|
+
|
4
|
+
## Currencylayer API
|
5
|
+
|
6
|
+
~~~ json
|
7
|
+
{
|
8
|
+
"timestamp": 1441101909,
|
9
|
+
"source": "USD",
|
10
|
+
"quotes": {
|
11
|
+
/* 168 currencies */
|
12
|
+
"USDAUD": 1.413637,
|
13
|
+
"USDCAD": 1.316495,
|
14
|
+
"USDCHF": 0.96355,
|
15
|
+
"USDEUR": 0.888466,
|
16
|
+
"USDBTC": 0.004322, /* Includes Bitcoin currency! */
|
17
|
+
...
|
18
|
+
}
|
19
|
+
}
|
20
|
+
~~~
|
21
|
+
|
22
|
+
See more about Currencylayer product plans on https://currencylayer.com/product.
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Add this line to your application's Gemfile:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem 'currencylayer-historical-bank'
|
30
|
+
```
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install currencylayer-historical-bank
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
~~~ ruby
|
43
|
+
# Minimal requirements
|
44
|
+
require 'money/bank/currencylayer_historical_bank'
|
45
|
+
mclb = Money::Bank::CurrencylayerHistoricalBank.new
|
46
|
+
mclb.access_key = 'your access_key from https://currencylayer.com/product'
|
47
|
+
|
48
|
+
# Update rates (get new rates from remote if expired or access rates from cache)
|
49
|
+
mclb.update_rates
|
50
|
+
|
51
|
+
# Force update rates from remote and store in cache
|
52
|
+
# mclb.update_rates(true)
|
53
|
+
|
54
|
+
# (optional)
|
55
|
+
# Set the base currency for all rates. By default, USD is used.
|
56
|
+
# CurrencylayerHistoricalBank only allows USD as base currency for the free plan users.
|
57
|
+
mclb.source = 'EUR'
|
58
|
+
|
59
|
+
# (optional)
|
60
|
+
# Set the seconds after than the current rates are automatically expired
|
61
|
+
# by default, they never expire, in this example 1 day.
|
62
|
+
mclb.ttl_in_seconds = 86400
|
63
|
+
|
64
|
+
# (optional)
|
65
|
+
# Use https to fetch rates from CurrencylayerHistoricalBank
|
66
|
+
# CurrencylayerHistoricalBank only allows http as connection for the free plan users.
|
67
|
+
mclb.secure_connection = true
|
68
|
+
|
69
|
+
# Define cache (string or pathname)
|
70
|
+
mclb.cache = 'path/to/file/cache'
|
71
|
+
|
72
|
+
# Set money default bank to currencylayer bank
|
73
|
+
Money.default_bank = mclb
|
74
|
+
~~~
|
75
|
+
|
76
|
+
### More methods
|
77
|
+
|
78
|
+
~~~ ruby
|
79
|
+
mclb = Money::Bank::CurrencylayerHistoricalBank.new
|
80
|
+
|
81
|
+
# Returns the base currency set for all rates.
|
82
|
+
mclb.source
|
83
|
+
|
84
|
+
# Expires rates if the expiration time is reached.
|
85
|
+
mclb.expire_rates!
|
86
|
+
|
87
|
+
# Return true if the expiration time is reached.
|
88
|
+
mclb.expired?
|
89
|
+
|
90
|
+
# Get the API source url.
|
91
|
+
mclb.source_url
|
92
|
+
|
93
|
+
# Get the rates timestamp of the last API request.
|
94
|
+
mclb.rates_timestamp
|
95
|
+
~~~
|
96
|
+
|
97
|
+
### How to exchange
|
98
|
+
|
99
|
+
~~~ ruby
|
100
|
+
# Exchange 1000 cents (10.0 USD) to EUR
|
101
|
+
Money.new(1000, 'USD').exchange_to('EUR') # => #<Money fractional:89 currency:EUR>
|
102
|
+
Money.new(1000, 'USD').exchange_to('EUR').to_f # => 8.9
|
103
|
+
|
104
|
+
# Format
|
105
|
+
Money.new(1000, 'USD').exchange_to('EUR').format # => €8.90
|
106
|
+
|
107
|
+
# Get the rate
|
108
|
+
Money.default_bank.get_rate('USD', 'CAD') # => 0.9
|
109
|
+
~~~
|
110
|
+
|
111
|
+
See more on https://github.com/RubyMoney/money.
|
112
|
+
|
113
|
+
### Using gem money-rails
|
114
|
+
|
115
|
+
You can also use it in Rails with the gem [money-rails](https://github.com/RubyMoney/money-rails).
|
116
|
+
|
117
|
+
~~~ ruby
|
118
|
+
require 'money/bank/currencylayer_bank'
|
119
|
+
|
120
|
+
MoneyRails.configure do |config|
|
121
|
+
mclb = Money::Bank::CurrencylayerHistoricalBank.new
|
122
|
+
mclb.access_key = 'your access_key from https://currencylayer.com/product'
|
123
|
+
mclb.update_rates
|
124
|
+
|
125
|
+
config.default_bank = mclb
|
126
|
+
end
|
127
|
+
~~~
|
128
|
+
|
129
|
+
### Cache
|
130
|
+
|
131
|
+
You can also provide a Proc as a cache to provide your own caching mechanism
|
132
|
+
perhaps with Redis or just a thread safe `Hash` (global). For example:
|
133
|
+
|
134
|
+
~~~ ruby
|
135
|
+
mclb.cache = Proc.new do |v|
|
136
|
+
key = 'money:currencylayer_bank'
|
137
|
+
if v
|
138
|
+
Thread.current[key] = v
|
139
|
+
else
|
140
|
+
Thread.current[key]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
~~~
|
144
|
+
|
145
|
+
## Process
|
146
|
+
|
147
|
+
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.
|
148
|
+
|
149
|
+
## Tests
|
150
|
+
|
151
|
+
You can place your own key on a file or environment
|
152
|
+
variable named TEST_ACCESS_KEY and then run:
|
153
|
+
|
154
|
+
~~~
|
155
|
+
bundle exec rake
|
156
|
+
~~~
|
157
|
+
|
158
|
+
## Refs
|
159
|
+
|
160
|
+
* Gem [money](https://github.com/RubyMoney/money)
|
161
|
+
* Gem [money-open-exchange-rates](https://github.com/spk/money-open-exchange-rates)
|
162
|
+
* Gem [money-historical-bank](https://github.com/atwam/money-historical-bank)
|
163
|
+
|
164
|
+
## Other Implementations
|
165
|
+
|
166
|
+
* Gem [currencylayer](https://github.com/askuratovsky/currencylayer)
|
167
|
+
* Gem [money-open-exchange-rates](https://github.com/spk/money-open-exchange-rates)
|
168
|
+
* Gem [money-historical-bank](https://github.com/atwam/money-historical-bank)
|
169
|
+
* Gem [eu_central_bank](https://github.com/RubyMoney/eu_central_bank)
|
170
|
+
* Gem [nordea](https://github.com/matiaskorhonen/nordea)
|
171
|
+
* Gem [google_currency](https://github.com/RubyMoney/google_currency)
|
172
|
+
|
173
|
+
## Contributing
|
174
|
+
|
175
|
+
1. Fork it ( https://github.com/[your-username]/currencylayer-historical-bank/fork )
|
176
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
177
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
178
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
179
|
+
5. Create a new Pull Request
|
180
|
+
|
181
|
+
## License
|
182
|
+
|
183
|
+
The MIT License
|
184
|
+
|
185
|
+
Copyright (c) 2017
|
@@ -0,0 +1,301 @@
|
|
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
|
+
# rubocop:disable Metrics/ClassLength
|
18
|
+
class CurrencylayerHistoricalBank < Money::Bank::VariableExchange
|
19
|
+
# CurrencylayerBank url
|
20
|
+
CL_URL = 'http://apilayer.net/api/live'.freeze
|
21
|
+
# CurrencylayerBank historical url
|
22
|
+
CL_HISTORICAL_URL = 'http://apilayer.net/api/historical'.freeze
|
23
|
+
# CurrencylayerBank secure url
|
24
|
+
CL_SECURE_URL = CL_URL.gsub('http:', 'https:').freeze
|
25
|
+
# Default base currency
|
26
|
+
CL_SOURCE = 'USD'.freeze
|
27
|
+
|
28
|
+
# Use https to fetch rates from CurrencylayerBank
|
29
|
+
# CurrencylayerBank only allows http as connection
|
30
|
+
# for the free plan users.
|
31
|
+
attr_accessor :secure_connection
|
32
|
+
|
33
|
+
# API must have a valid access_key
|
34
|
+
attr_accessor :access_key
|
35
|
+
|
36
|
+
# Fetch historical rates on selected date
|
37
|
+
attr_accessor :historical_date
|
38
|
+
|
39
|
+
# Cache accessor, can be a String or a Proc
|
40
|
+
attr_accessor :cache
|
41
|
+
|
42
|
+
# Rates expiration Time
|
43
|
+
attr_reader :rates_expiration
|
44
|
+
|
45
|
+
# Parsed CurrencylayerBank result as Hash
|
46
|
+
attr_reader :rates
|
47
|
+
|
48
|
+
# Seconds after than the current rates are automatically expired
|
49
|
+
attr_reader :ttl_in_seconds
|
50
|
+
|
51
|
+
# Set the base currency for all rates. By default, USD is used.
|
52
|
+
# CurrencylayerBank only allows USD as base currency
|
53
|
+
# for the free plan users.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# source = 'USD'
|
57
|
+
#
|
58
|
+
# @param value [String] Currency code, ISO 3166-1 alpha-3
|
59
|
+
#
|
60
|
+
# @return [String] chosen base currency
|
61
|
+
def source=(value)
|
62
|
+
@source = Money::Currency.find(value.to_s).try(:iso_code) || CL_SOURCE
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the base currency for all rates. By default, USD is used.
|
66
|
+
# @return [String] base currency
|
67
|
+
def source
|
68
|
+
@source ||= CL_SOURCE
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set the seconds after than the current rates are automatically expired
|
72
|
+
# by default, they never expire.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# ttl_in_seconds = 86400 # will expire the rates in one day
|
76
|
+
#
|
77
|
+
# @param value [Integer] time to live in seconds
|
78
|
+
#
|
79
|
+
# @return [Integer] chosen time to live in seconds
|
80
|
+
def ttl_in_seconds=(value)
|
81
|
+
@ttl_in_seconds = value
|
82
|
+
refresh_rates_expiration!
|
83
|
+
@ttl_in_seconds
|
84
|
+
end
|
85
|
+
|
86
|
+
# Update all rates from CurrencylayerBank JSON
|
87
|
+
# @return [Array] array of exchange rates
|
88
|
+
def update_rates(straight = false)
|
89
|
+
exchange_rates(straight).each do |exchange_rate|
|
90
|
+
currency = exchange_rate.first[3..-1]
|
91
|
+
rate = exchange_rate.last
|
92
|
+
next unless Money::Currency.find(currency)
|
93
|
+
add_rate(source, currency, rate)
|
94
|
+
add_rate(currency, source, 1.0 / rate)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Override Money `get_rate` method for caching
|
99
|
+
# @param [String] from_currency Currency ISO code. ex. 'USD'
|
100
|
+
# @param [String] to_currency Currency ISO code. ex. 'CAD'
|
101
|
+
#
|
102
|
+
# @return [Numeric] rate.
|
103
|
+
def get_rate(from_currency, to_currency, opts = {}) # rubocop:disable all
|
104
|
+
@historical_date = Date.parse(opts[:date]).strftime("%Y-%m-%d") if opts[:date]
|
105
|
+
expire_rates!
|
106
|
+
rate = super
|
107
|
+
unless rate
|
108
|
+
# Tries to calculate an inverse rate
|
109
|
+
inverse_rate = super(to_currency, from_currency, opts)
|
110
|
+
if inverse_rate
|
111
|
+
rate = 1.0 / inverse_rate
|
112
|
+
add_rate(from_currency, to_currency, rate)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
unless rate
|
116
|
+
# Tries to calculate a pair rate using base currency rate
|
117
|
+
from_base_rate = super(source, from_currency, opts)
|
118
|
+
unless from_base_rate
|
119
|
+
from_inverse_rate = super(from_currency, source, opts)
|
120
|
+
from_base_rate = 1.0 / from_inverse_rate if from_inverse_rate
|
121
|
+
end
|
122
|
+
to_base_rate = super(source, to_currency, opts)
|
123
|
+
unless to_base_rate
|
124
|
+
to_inverse_rate = super(to_currency, source, opts)
|
125
|
+
to_base_rate = 1.0 / to_inverse_rate if to_inverse_rate
|
126
|
+
end
|
127
|
+
if to_base_rate && from_base_rate
|
128
|
+
rate = to_base_rate / from_base_rate
|
129
|
+
add_rate(from_currency, to_currency, rate)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
rate
|
133
|
+
end
|
134
|
+
|
135
|
+
# Fetch new rates if cached rates are expired
|
136
|
+
# @return [Boolean] true if rates are expired and updated from remote
|
137
|
+
def expire_rates!
|
138
|
+
if expired?
|
139
|
+
update_rates(true)
|
140
|
+
true
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Check if rates are expired
|
147
|
+
# @return [Boolean] true if rates are expired
|
148
|
+
def expired?
|
149
|
+
return true if historical_date
|
150
|
+
rates_expiration ? rates_expiration <= Time.now : true
|
151
|
+
end
|
152
|
+
|
153
|
+
# Source url of CurrencylayerBank
|
154
|
+
# defined with access_key and secure_connection
|
155
|
+
# @return [String] the remote API url
|
156
|
+
def source_url
|
157
|
+
raise NoAccessKey if access_key.nil? || access_key.empty?
|
158
|
+
cl_url = historical_date ? CL_HISTORICAL_URL : CL_URL
|
159
|
+
cl_url = CL_SECURE_URL if secure_connection
|
160
|
+
|
161
|
+
base_url = "#{cl_url}?source=#{source}&access_key=#{access_key}"
|
162
|
+
if historical_date
|
163
|
+
date_url = "&date=#{historical_date}"
|
164
|
+
base_url = base_url + date_url
|
165
|
+
end
|
166
|
+
|
167
|
+
base_url
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get the timestamp of rates
|
171
|
+
# @return [Time] time object or nil
|
172
|
+
def rates_timestamp
|
173
|
+
@rates_timestamp ||= init_rates_timestamp
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
# Sets the rates timestamp from parsed JSON content
|
179
|
+
#
|
180
|
+
# @example
|
181
|
+
# set_rates_timestamp("{\"timestamp\": 1441049528,
|
182
|
+
# \"quotes\": {\"USDAED\": 3.67304}}")
|
183
|
+
#
|
184
|
+
# @param raw_rates [String] parsed JSON content, default is nil
|
185
|
+
# @return [Time] time object with rates timestamp
|
186
|
+
def init_rates_timestamp(raw_rates = nil)
|
187
|
+
raw = raw_rates || raw_rates_careful
|
188
|
+
@rates_timestamp = Time.at(raw['timestamp']) if raw.key?('timestamp')
|
189
|
+
end
|
190
|
+
|
191
|
+
# Store the provided text data by calling the proc method provided
|
192
|
+
# for the cache, or write to the cache file.
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# store_in_cache("{\"quotes\": {\"USDAED\": 3.67304}}")
|
196
|
+
#
|
197
|
+
# @param text [String] parsed JSON content
|
198
|
+
# @return [String,Integer]
|
199
|
+
def store_in_cache(text)
|
200
|
+
if cache.is_a?(Proc)
|
201
|
+
cache.call(text)
|
202
|
+
elsif cache.is_a?(String) || cache.is_a?(Pathname)
|
203
|
+
write_to_file(text)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Writes content to file cache
|
208
|
+
# @param text [String] parsed JSON content
|
209
|
+
# @return [String,Integer]
|
210
|
+
def write_to_file(text)
|
211
|
+
open(cache, 'w') do |f|
|
212
|
+
f.write(text)
|
213
|
+
end
|
214
|
+
rescue Errno::ENOENT
|
215
|
+
raise InvalidCache
|
216
|
+
end
|
217
|
+
|
218
|
+
# Read from cache when exist
|
219
|
+
# @return [Proc,String] parsed JSON content
|
220
|
+
def read_from_cache
|
221
|
+
if cache.is_a?(Proc)
|
222
|
+
cache.call(nil)
|
223
|
+
elsif (cache.is_a?(String) || cache.is_a?(Pathname)) &&
|
224
|
+
File.exist?(cache)
|
225
|
+
open(cache).read
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Get remote content and store in cache
|
230
|
+
# @return [String] unparsed JSON content
|
231
|
+
def read_from_url
|
232
|
+
text = open_url
|
233
|
+
if valid_rates?(text)
|
234
|
+
refresh_rates_expiration!
|
235
|
+
store_in_cache(text) if cache
|
236
|
+
end
|
237
|
+
text
|
238
|
+
end
|
239
|
+
|
240
|
+
# Opens an url and reads the content
|
241
|
+
# @return [String] unparsed JSON content
|
242
|
+
def open_url
|
243
|
+
open(source_url).read
|
244
|
+
end
|
245
|
+
|
246
|
+
# Check validity of rates response only for store in cache
|
247
|
+
#
|
248
|
+
# @example
|
249
|
+
# valid_rates?("{\"quotes\": {\"USDAED\": 3.67304}}")
|
250
|
+
#
|
251
|
+
# @param [String] text is JSON content
|
252
|
+
# @return [Boolean] valid or not
|
253
|
+
def valid_rates?(text)
|
254
|
+
parsed = JSON.parse(text)
|
255
|
+
parsed && parsed.key?('quotes')
|
256
|
+
rescue JSON::ParserError
|
257
|
+
false
|
258
|
+
end
|
259
|
+
|
260
|
+
# Get exchange rates with different strategies
|
261
|
+
#
|
262
|
+
# @example
|
263
|
+
# exchange_rates(true)
|
264
|
+
# exchange_rates
|
265
|
+
#
|
266
|
+
# @param straight [Boolean] true for straight, default is careful
|
267
|
+
# @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
|
268
|
+
def exchange_rates(straight = false)
|
269
|
+
@rates = if straight
|
270
|
+
raw_rates_straight['quotes']
|
271
|
+
else
|
272
|
+
raw_rates_careful['quotes']
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Get raw exchange rates from cache and then from url
|
277
|
+
# @return [String] JSON content
|
278
|
+
def raw_rates_careful
|
279
|
+
JSON.parse(read_from_cache.to_s)
|
280
|
+
rescue JSON::ParserError
|
281
|
+
raw_rates_straight
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get raw exchange rates from url
|
285
|
+
# @return [String] JSON content
|
286
|
+
def raw_rates_straight
|
287
|
+
raw_rates = JSON.parse(read_from_url)
|
288
|
+
init_rates_timestamp(raw_rates)
|
289
|
+
raw_rates
|
290
|
+
rescue JSON::ParserError
|
291
|
+
{ 'quotes' => {} }
|
292
|
+
end
|
293
|
+
|
294
|
+
# Refresh expiration from now
|
295
|
+
# return [Time] new expiration time
|
296
|
+
def refresh_rates_expiration!
|
297
|
+
@rates_expiration = Time.now + ttl_in_seconds unless ttl_in_seconds.nil?
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
your access_key from https://currencylayer.com/product
|
@@ -0,0 +1,296 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
3
|
+
|
4
|
+
describe Money::Bank::CurrencylayerHistoricalBank do
|
5
|
+
subject { Money::Bank::CurrencylayerHistoricalBank.new }
|
6
|
+
let(:url) { Money::Bank::CurrencylayerHistoricalBank::CL_URL }
|
7
|
+
let(:secure_url) { Money::Bank::CurrencylayerHistoricalBank::CL_SECURE_URL }
|
8
|
+
let(:source) { Money::Bank::CurrencylayerHistoricalBank::CL_SOURCE }
|
9
|
+
let(:temp_cache_path) do
|
10
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'temp.json'))
|
11
|
+
end
|
12
|
+
let(:data_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).source_url { data_path }
|
21
|
+
subject.update_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 'cache rates' do
|
65
|
+
before do
|
66
|
+
subject.access_key = TEST_ACCESS_KEY
|
67
|
+
subject.cache = temp_cache_path
|
68
|
+
stub(subject).source_url { data_path }
|
69
|
+
subject.update_rates
|
70
|
+
end
|
71
|
+
|
72
|
+
after do
|
73
|
+
File.delete(temp_cache_path) if File.exist?(temp_cache_path)
|
74
|
+
end
|
75
|
+
|
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
|
82
|
+
end
|
83
|
+
|
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
|
89
|
+
end
|
90
|
+
|
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
|
96
|
+
end
|
97
|
+
|
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
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'no cache' do
|
107
|
+
before do
|
108
|
+
subject.cache = nil
|
109
|
+
subject.access_key = TEST_ACCESS_KEY
|
110
|
+
stub(subject).source_url { data_path }
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should get from url' do
|
114
|
+
subject.update_rates
|
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 }
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should raise an error if invalid path is given' do
|
127
|
+
proc { subject.update_rates }.must_raise Money::Bank::InvalidCache
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe 'using proc for cache' do
|
132
|
+
before :each do
|
133
|
+
@global_rates = nil
|
134
|
+
subject.cache = proc { |v|
|
135
|
+
if v
|
136
|
+
@global_rates = v
|
137
|
+
else
|
138
|
+
@global_rates
|
139
|
+
end
|
140
|
+
}
|
141
|
+
subject.access_key = TEST_ACCESS_KEY
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should get from url normally' do
|
145
|
+
stub(subject).source_url { data_path }
|
146
|
+
subject.update_rates
|
147
|
+
subject.rates.wont_be_empty
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should save from url and get from cache' do
|
151
|
+
stub(subject).source_url { data_path }
|
152
|
+
subject.update_rates
|
153
|
+
@global_rates.wont_be_empty
|
154
|
+
dont_allow(subject).source_url
|
155
|
+
subject.update_rates
|
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://'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#update_rates' do
|
185
|
+
before do
|
186
|
+
subject.access_key = TEST_ACCESS_KEY
|
187
|
+
subject.cache = data_path
|
188
|
+
stub(subject).source_url { data_path }
|
189
|
+
subject.update_rates
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should update itself with exchange rates from CurrencylayerBank' do
|
193
|
+
subject.rates.keys.each do |currency|
|
194
|
+
next unless Money::Currency.find(currency)
|
195
|
+
subject.get_rate('USD', currency).must_be :>, 0
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should not return 0 with integer rate' do
|
200
|
+
wtf = {
|
201
|
+
priority: 1,
|
202
|
+
iso_code: 'WTF',
|
203
|
+
name: 'WTF',
|
204
|
+
symbol: 'WTF',
|
205
|
+
subunit: 'Cent',
|
206
|
+
subunit_to_unit: 1000,
|
207
|
+
separator: '.',
|
208
|
+
delimiter: ','
|
209
|
+
}
|
210
|
+
Money::Currency.register(wtf)
|
211
|
+
subject.add_rate('USD', 'WTF', 2)
|
212
|
+
subject.add_rate('WTF', 'USD', 2)
|
213
|
+
subject.exchange_with(5000.to_money('WTF'), 'USD').cents.wont_equal 0
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#access_key' do
|
218
|
+
before do
|
219
|
+
subject.cache = temp_cache_path
|
220
|
+
stub(OpenURI::OpenRead).open(url) { File.read data_path }
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should raise an error if no access key is set' do
|
224
|
+
proc { subject.update_rates }.must_raise Money::Bank::NoAccessKey
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#expire_rates!' do
|
229
|
+
before do
|
230
|
+
subject.access_key = TEST_ACCESS_KEY
|
231
|
+
subject.ttl_in_seconds = 1000
|
232
|
+
@old_usd_eur_rate = 0.655
|
233
|
+
# see test/live.json +54
|
234
|
+
@new_usd_eur_rate = 0.886584
|
235
|
+
subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
|
236
|
+
subject.cache = temp_cache_path
|
237
|
+
stub(subject).source_url { data_path }
|
238
|
+
end
|
239
|
+
|
240
|
+
after do
|
241
|
+
File.delete(temp_cache_path) if File.exist?(temp_cache_path)
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'when the ttl has expired' do
|
245
|
+
it 'should update the rates' do
|
246
|
+
Timecop.freeze(subject.rates_timestamp + 1000) do
|
247
|
+
subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
|
248
|
+
end
|
249
|
+
Timecop.freeze(subject.rates_timestamp + 1001) do
|
250
|
+
subject.get_rate('USD', 'EUR').wont_equal @old_usd_eur_rate
|
251
|
+
subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'updates the next expiration time' do
|
256
|
+
Timecop.freeze(subject.rates_timestamp + 1001) do
|
257
|
+
exp_time = subject.rates_timestamp + 1000
|
258
|
+
subject.expire_rates!
|
259
|
+
subject.rates_expiration.must_equal exp_time
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe 'when the ttl has not expired' do
|
265
|
+
it 'not should update the rates' do
|
266
|
+
subject.update_rates
|
267
|
+
exp_time = subject.rates_expiration
|
268
|
+
subject.expire_rates!
|
269
|
+
subject.rates_expiration.must_equal exp_time
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe '#rates_timestamp' do
|
275
|
+
before do
|
276
|
+
subject.access_key = TEST_ACCESS_KEY
|
277
|
+
subject.cache = temp_cache_path
|
278
|
+
stub(subject).source_url { data_path }
|
279
|
+
end
|
280
|
+
|
281
|
+
after do
|
282
|
+
File.delete(temp_cache_path) if File.exist?(temp_cache_path)
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'should return 1970-01-01 datetime if no rates' do
|
286
|
+
stub(subject).open_url { '' }
|
287
|
+
subject.update_rates
|
288
|
+
subject.rates_timestamp.must_equal Time.at(0)
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'should return a Time object' do
|
292
|
+
subject.update_rates
|
293
|
+
subject.rates_timestamp.class.must_equal Time
|
294
|
+
end
|
295
|
+
end
|
296
|
+
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
|
+
}
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'rr'
|
4
|
+
require 'money/bank/currencylayer_historical_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
|
+
raise "Please add a valid access key to file #{TEST_ACCESS_KEY_PATH} or to " \
|
14
|
+
' TEST_ACCESS_KEY environment'
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: currencylayer-historical-bank
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ildus Sadykov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-13 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.1
|
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.1
|
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.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
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: rr
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 12.0.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 12.0.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: timecop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.8.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.8.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.47.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.47.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: inch
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.7.1
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.7.1
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 3.5.0
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 3.5.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: webmock
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: bundler
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '1.7'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '1.7'
|
195
|
+
description: A gem that calculates the historical exchange rate using published rates
|
196
|
+
from currencylayer.com. Compatible with the money gem.
|
197
|
+
email: ildus@meyvndigital.co.uk
|
198
|
+
executables: []
|
199
|
+
extensions: []
|
200
|
+
extra_rdoc_files:
|
201
|
+
- README.md
|
202
|
+
files:
|
203
|
+
- Gemfile
|
204
|
+
- LICENSE
|
205
|
+
- README.md
|
206
|
+
- lib/money/bank/currencylayer_historical_bank.rb
|
207
|
+
- lib/money/version.rb
|
208
|
+
- test/TEST_ACCESS_KEY
|
209
|
+
- test/currencylayer_bank_test.rb
|
210
|
+
- test/live.json
|
211
|
+
- test/test_helper.rb
|
212
|
+
homepage: http://github.com/IldusSadykov/currencylayer-historical-bank
|
213
|
+
licenses:
|
214
|
+
- MIT
|
215
|
+
metadata: {}
|
216
|
+
post_install_message:
|
217
|
+
rdoc_options: []
|
218
|
+
require_paths:
|
219
|
+
- lib
|
220
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - ">="
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: 2.1.2
|
225
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
requirements: []
|
231
|
+
rubyforge_project:
|
232
|
+
rubygems_version: 2.5.1
|
233
|
+
signing_key:
|
234
|
+
specification_version: 4
|
235
|
+
summary: A gem that calculates the historical exchange rate using published rates
|
236
|
+
from currencylayer.com.
|
237
|
+
test_files:
|
238
|
+
- test/currencylayer_bank_test.rb
|
239
|
+
has_rdoc:
|