money-transferwise-bank 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 817504b5ec5f4855c4dab4dee38e293b38c17c00
4
+ data.tar.gz: 10e7f4689b4c2d9b76dbb259ea4ac3597f0ed7a4
5
+ SHA512:
6
+ metadata.gz: 6e1cddd67112f83e0a114577819aefd4c095799f4885ffa5c0a24abbb0f140b1111dedd6e7cb1fb9ca468ed6a616e3b6b7f9864bb523cabe07f9cda43da1f875
7
+ data.tar.gz: 4afb9b242eb701c0ff53ac5060afdfdaf00e9932f95d70a70a472ec652a5631ab3f9eb4bfc41cc6489f675294ca8f8c9d5ed0638e985e2c0fa4e724ccac4e0d4
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :test, :development do
8
+ platforms :ruby_19, :jruby_19 do
9
+ gem 'tins', '~> 1.6'
10
+ end
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 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,221 @@
1
+ # Money TransferWise Bank
2
+
3
+ <!-- [![Gem Version](https://badge.fury.io/rb/money-transferwise-bank.svg)](https://rubygems.org/gems/money-transferwise-bank)
4
+ [![Gem](https://img.shields.io/gem/dt/money-transferwise-bank.svg?maxAge=2592000)](https://rubygems.org/gems/money-transferwise-bank) -->
5
+ [![Build Status](https://secure.travis-ci.org/mikelkew/money-transferwise-bank.svg?branch=master)](https://travis-ci.org/mikelkew/money-transferwise-bank)
6
+ [![Code Climate](https://api.codeclimate.com/v1/badges/2f2915f2fb539324fe3f/maintainability)](https://codeclimate.com/github/mikelkew/money-transferwise-bank)
7
+ [![Inline Docs](http://inch-ci.org/github/mikelkew/money-transferwise-bank.svg?branch=master)](http://inch-ci.org/github/mikelkew/money-transferwise-bank)
8
+ [![License](https://img.shields.io/github/license/mikelkew/money-transferwise-bank.svg)](http://opensource.org/licenses/MIT)
9
+
10
+ A gem that calculates the exchange rate using available rates from the [TransferWise](https://transferwise.com/) Exchange Rate API.
11
+
12
+ ## TransferWise API
13
+
14
+ ~~~ json
15
+ [
16
+ /* 157 currencies */
17
+ {
18
+ "rate": 1.74145,
19
+ "source": "USD",
20
+ "target": "BGN",
21
+ "time": "2019-06-05T17:48:33+0000"
22
+ },
23
+ {
24
+ "rate": 1180.34,
25
+ "source": "USD",
26
+ "target": "KRW",
27
+ "time": "2019-06-05T17:48:33+0000"
28
+ },
29
+ ...
30
+ ]
31
+ ~~~
32
+
33
+ See more information about the API in the [TransferWise API Documentation](https://api-docs.transferwise.com/#exchange-rates)
34
+
35
+ ## Features
36
+
37
+ * supports 157 currencies
38
+ * precision of rates up to 6 digits after point
39
+ * uses fast and reliable JSON API
40
+ * average response time < 400ms
41
+ * supports caching currency rates
42
+ * calculates every pair rate calculating inverse rate or using base currency rate
43
+ * supports multiple server instances, thread safe
44
+
45
+ ## Installation
46
+
47
+ Add this line to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'money-transferwise-bank'
51
+ ```
52
+
53
+ And then execute:
54
+
55
+ $ bundle
56
+
57
+ Or install it yourself as:
58
+
59
+ $ gem install money-transferwise-bank
60
+
61
+ ## Usage
62
+
63
+ ~~~ ruby
64
+ # Minimal requirements
65
+ require 'money/bank/transferwise_bank'
66
+ mtwb = Money::Bank::TransferwiseBank.new
67
+ mtwb.access_key = 'your access_key from https://transferwise.com'
68
+
69
+ # Update rates (get new rates from remote if expired or access rates from cache)
70
+ mtwb.update_rates
71
+
72
+ # Force update rates from remote and store in cache
73
+ # mtwb.update_rates(true)
74
+
75
+ # (optional)
76
+ # Set the base currency for all rates. By default, USD is used.
77
+ mtwb.source = 'EUR'
78
+
79
+ # (optional)
80
+ # Set the seconds after than the current rates are automatically expired
81
+ # by default, they never expire, in this example 1 day.
82
+ mtwb.ttl_in_seconds = 86400
83
+
84
+ # (optional)
85
+ # Option to use the Sandbox version of the TransferWise API.
86
+ # By default, this is false and the live API is used.
87
+ mtwb.use_sandbox = true
88
+
89
+ # (optional)
90
+ # Option to raise an error on failure to connect to the API or parse
91
+ # the response. By default, this is true, but the ability to disable
92
+ # it is useful when developing without an active internet connection.
93
+ mtwb.raise_on_failure = false
94
+
95
+ # Define cache (string or pathname)
96
+ mtwb.cache = 'path/to/file/cache'
97
+
98
+ # Set money default bank to transferwise bank
99
+ Money.default_bank = mtwb
100
+ ~~~
101
+
102
+ ### More methods
103
+
104
+ ~~~ ruby
105
+ mtwb = Money::Bank::TransferwiseBank.new
106
+
107
+ # Returns the base currency set for all rates.
108
+ mtwb.source
109
+
110
+ # Expires rates if the expiration time is reached.
111
+ mtwb.expire_rates!
112
+
113
+ # Returns true if the expiration time is reached.
114
+ mtwb.expired?
115
+
116
+ # Get the API source url.
117
+ mtwb.source_url
118
+
119
+ # Get the rates timestamp of the last API request.
120
+ mtwb.rates_timestamp
121
+
122
+ # Get the rates timestamp of loaded rates in memory.
123
+ moxb.rates_mem_timestamp
124
+ ~~~
125
+
126
+ ### How to exchange
127
+
128
+ ~~~ ruby
129
+ # Exchange 1000 cents (10.0 USD) to EUR
130
+ Money.new(1000, 'USD').exchange_to('EUR') # => #<Money fractional:89 currency:EUR>
131
+ Money.new(1000, 'USD').exchange_to('EUR').to_f # => 8.9
132
+
133
+ # Format
134
+ Money.new(1000, 'USD').exchange_to('EUR').format # => €8.90
135
+
136
+ # Get the rate
137
+ Money.default_bank.get_rate('USD', 'CAD') # => 0.9
138
+ ~~~
139
+
140
+ See more on https://github.com/RubyMoney/money.
141
+
142
+ ### Using gem money-rails
143
+
144
+ You can also use it in Rails with the gem [money-rails](https://github.com/RubyMoney/money-rails).
145
+
146
+ ~~~ ruby
147
+ require 'money/bank/transferwise_bank'
148
+
149
+ MoneyRails.configure do |config|
150
+ mtwb = Money::Bank::TransferwiseBank.new
151
+ mtwb.access_key = 'your access_key from https://transferwise.com/product'
152
+ mtwb.update_rates
153
+
154
+ config.default_bank = mtwb
155
+ end
156
+ ~~~
157
+
158
+ ### Cache
159
+
160
+ You can also provide a Proc as a cache to provide your own caching mechanism
161
+ perhaps with Redis or just a thread safe `Hash` (global). For example:
162
+
163
+ ~~~ ruby
164
+ mtwb.cache = Proc.new do |v|
165
+ key = 'money:transferwise_bank'
166
+ if v
167
+ Thread.current[key] = v
168
+ else
169
+ Thread.current[key]
170
+ end
171
+ end
172
+ ~~~
173
+
174
+ ## Process
175
+
176
+ 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.
177
+
178
+ ## Tests
179
+
180
+ You can place your own key on a file or environment
181
+ variable named TEST_ACCESS_KEY and then run:
182
+
183
+ ~~~
184
+ bundle exec rake
185
+ ~~~
186
+
187
+ ## Refs
188
+
189
+ * Gem [money](https://github.com/RubyMoney/money)
190
+ * Gem [money-currencylayer-bank](https://github.com/phlegx/money-currencylayer-bank)
191
+ * Gem [money-openexchangerates-bank](https://github.com/phlegx/money-openexchangerates-bank)
192
+ * Gem [money-historical-bank](https://github.com/atwam/money-historical-bank)
193
+
194
+ ## Other Implementations
195
+
196
+ * Gem [currencylayer](https://github.com/askuratovsky/currencylayer)
197
+ * Gem [money-openexchangerates-bank](https://github.com/phlegx/money-openexchangerates-bank)
198
+ * Gem [money-open-exchange-rates](https://github.com/spk/money-open-exchange-rates)
199
+ * Gem [money-historical-bank](https://github.com/atwam/money-historical-bank)
200
+ * Gem [eu_central_bank](https://github.com/RubyMoney/eu_central_bank)
201
+ * Gem [nordea](https://github.com/matiaskorhonen/nordea)
202
+ * Gem [google_currency](https://github.com/RubyMoney/google_currency)
203
+
204
+ ## Contributors
205
+
206
+ * See [github.com/mikelkew/money-transferwise-bank](https://github.com/mikelkew/money-transferwise-bank/graphs/contributors).
207
+ * Inspired by [github.com/phlegx/money-currencylayer-bank](https://github.com/phlegx/money-currencylayer-bank/graphs/contributors).
208
+
209
+ ## Contributing
210
+
211
+ 1. Fork it ( https://github.com/[your-username]/money-transferwise-bank/fork )
212
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
213
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
214
+ 4. Push to the branch (`git push origin my-new-feature`)
215
+ 5. Create a new Pull Request
216
+
217
+ ## License
218
+
219
+ The MIT License
220
+
221
+ Copyright (c) 2019 Mikel Kew
@@ -0,0 +1,405 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'money'
4
+ require 'json'
5
+ require 'open-uri'
6
+ require 'httparty'
7
+
8
+ # Money gem class
9
+ class Money
10
+ # Build in memory rates store
11
+ module RatesStore
12
+ # Memory class
13
+ class Memory
14
+ # Add method to reset the build in memory store
15
+ # @param [Hash] rates Optional initial exchange rate data.
16
+ # @return [Object] store.
17
+ def reset!(rates = {})
18
+ transaction { @index = rates }
19
+ end
20
+ end
21
+ end
22
+
23
+ # https://github.com/RubyMoney/money#exchange-rate-stores
24
+ module Bank
25
+ # Invalid cache, file not found or cache empty
26
+ class InvalidCache < StandardError; end
27
+
28
+ # App id not set error
29
+ class NoAccessKey < StandardError; end
30
+
31
+ # TransferwiseBank base class
32
+ class TransferwiseBank < Money::Bank::VariableExchange
33
+ # TransferwiseBank url components
34
+ TW_SERVICE_HOST = 'api.transferwise.com'.freeze
35
+ TW_SERVICE_PATH = '/v1/rates'.freeze
36
+
37
+ TW_SANDBOX_SERVICE_HOST = 'api.sandbox.transferwise.tech'.freeze
38
+
39
+ # Default SSL Version
40
+ TW_SERVICE_SSL_VERSION = :TLSv1_2
41
+
42
+ # Default base currency
43
+ TW_SOURCE = 'USD'.freeze
44
+
45
+ # API must have a valid access_key
46
+ #
47
+ # @param value [String] API access key
48
+ # @return [String] chosen API access key
49
+ attr_accessor :access_key
50
+
51
+ # Cache accessor, can be a String or a Proc
52
+ #
53
+ # @param value [String,Pathname,Proc] cache system
54
+ # @return [String,Pathname,Proc] chosen cache system
55
+ attr_accessor :cache
56
+
57
+ # Parsed TransferwiseBank result as Hash
58
+ attr_reader :rates
59
+
60
+ # Get the timestamp of rates in memory
61
+ # @return [Time] time object or nil
62
+ attr_reader :rates_mem_timestamp
63
+
64
+ # Set the seconds after than the current rates are automatically expired
65
+ # by default, they never expire.
66
+ #
67
+ # @example
68
+ # ttl_in_seconds = 86400 # will expire the rates in one day
69
+ #
70
+ # @param value [Integer] time to live in seconds
71
+ # @return [Integer] chosen time to live in seconds
72
+ attr_writer :ttl_in_seconds
73
+
74
+ # Set the SSL Version used for requests to the API.
75
+ # By default, :TLSv1_2 is used.
76
+ #
77
+ # @example
78
+ # service_ssl_version = :TLSv1_1
79
+ #
80
+ # @param value [Symbol] SSL version from OpenSSL::SSL::SSLContext::METHODS
81
+ # @return [Symbol] chosen SSL version
82
+ attr_writer :service_ssl_version
83
+
84
+ # Option to use the Sandbox version of the TransferWise API.
85
+ # By default, this is false, and the live API is used.
86
+ #
87
+ # @example
88
+ # use_sandbox = true
89
+ #
90
+ # @param value [Boolean] should the sandbox api be used?
91
+ # @return [Boolean] is the sandbox api being used?
92
+ attr_writer :use_sandbox
93
+
94
+ # Option to raise an error on failure to connect to the API or parse
95
+ # the response. By default, this is true, but the ability to disable
96
+ # it is useful when developing without an active internet connection.
97
+ #
98
+ # @example
99
+ # raise_on_failure = false
100
+ #
101
+ # @param value [Boolean] should an error be raised on API failure?
102
+ # @return [Boolean] is an error to be raised on API failure?
103
+ attr_writer :raise_on_failure
104
+
105
+ # Set the base currency for all rates. By default, USD is used.
106
+ # TransferwiseBank only allows USD as base currency
107
+ # for the free plan users.
108
+ #
109
+ # @example
110
+ # source = 'USD'
111
+ #
112
+ # @param value [String] Currency code, ISO 3166-1 alpha-3
113
+ # @return [String] chosen base currency
114
+ def source=(value)
115
+ @source = Money::Currency.find(value.to_s).try(:iso_code) || TW_SOURCE
116
+ end
117
+
118
+ # Get the base currency for all rates. By default, USD is used.
119
+ # @return [String] base currency
120
+ def source
121
+ @source ||= TW_SOURCE
122
+ end
123
+
124
+ # Get the seconds after than the current rates are automatically expired
125
+ # by default, they never expire.
126
+ # @return [Integer] chosen time to live in seconds
127
+ def ttl_in_seconds
128
+ @ttl_in_seconds ||= 0
129
+ end
130
+
131
+ # Set the SSL Version used for requests to the API.
132
+ # By default, :TLSv1_2 is used.
133
+ # @return [Symbol] chosen SSL version
134
+ def service_ssl_version
135
+ @service_ssl_version ||= TW_SERVICE_SSL_VERSION
136
+ end
137
+
138
+ # Option to use the Sandbox version of the TransferWise API.
139
+ # By default, this is false, and the live API is used.
140
+ # @return [Boolean] is the sandbox api being used?
141
+ def use_sandbox
142
+ @use_sandbox = false if @use_sandbox.nil?
143
+ @use_sandbox
144
+ end
145
+
146
+ # Option to raise an error on failure to connect to the API or parse
147
+ # the response. By default, this is true, but the ability to disable
148
+ # it is useful when developing without an active internet connection.
149
+ # @return [Boolean] is an error to be raised on API failure?
150
+ def raise_on_failure
151
+ @raise_on_failure = true if @raise_on_failure.nil?
152
+ @raise_on_failure
153
+ end
154
+
155
+ # Update all rates from TrasferwiseBank JSON
156
+ # @return [Array] array of exchange rates
157
+ def update_rates(straight = false)
158
+ new_rates = exchange_rates(straight)
159
+ return if new_rates.first.empty?
160
+ store.reset!
161
+ rates = new_rates.each do |exchange_rate|
162
+ currency = exchange_rate['target']
163
+ rate = exchange_rate['rate']
164
+ next unless Money::Currency.find(currency)
165
+ add_rate(source, currency, rate)
166
+ add_rate(currency, source, 1.0 / rate)
167
+ end
168
+ @rates_mem_timestamp = rates_timestamp
169
+ rates
170
+ end
171
+
172
+ # Override Money `add_rate` method for caching
173
+ # @param [String] from_currency Currency ISO code. ex. 'USD'
174
+ # @param [String] to_currency Currency ISO code. ex. 'CAD'
175
+ # @param [Numeric] rate Rate to use when exchanging currencies.
176
+ # @return [Numeric] rate.
177
+ def add_rate(from_currency, to_currency, rate)
178
+ super
179
+ end
180
+
181
+ # Alias super method
182
+ alias super_get_rate get_rate
183
+
184
+ # Override Money `get_rate` method for caching
185
+ # @param [String] from_currency Currency ISO code. ex. 'USD'
186
+ # @param [String] to_currency Currency ISO code. ex. 'CAD'
187
+ # @param [Hash] opts Options hash to set special parameters.
188
+ # @return [Numeric] rate.
189
+ def get_rate(from_currency, to_currency, opts = {})
190
+ expire_rates!
191
+ rate = get_rate_or_calc_inverse(from_currency, to_currency, opts)
192
+ rate || calc_pair_rate_using_base(from_currency, to_currency, opts)
193
+ end
194
+
195
+ # Fetch new rates if cached rates are expired or stale
196
+ # @return [Boolean] true if rates are expired and updated from remote
197
+ def expire_rates!
198
+ if expired?
199
+ update_rates(true)
200
+ true
201
+ elsif stale?
202
+ update_rates
203
+ true
204
+ else
205
+ false
206
+ end
207
+ end
208
+
209
+ # Check if rates are expired
210
+ # @return [Boolean] true if rates are expired
211
+ def expired?
212
+ Time.now > rates_expiration
213
+ end
214
+
215
+ # Check if rates are stale
216
+ # Stale is true if rates are updated straight by another thread.
217
+ # The actual thread has always old rates in memory store.
218
+ # @return [Boolean] true if rates are stale
219
+ def stale?
220
+ rates_timestamp != rates_mem_timestamp
221
+ end
222
+
223
+ # Service host of TransferwiseBank API based on value
224
+ # of 'use_sandbox' option.
225
+ # @return [String] the remote API service host
226
+ def service_host
227
+ use_sandbox ? TW_SANDBOX_SERVICE_HOST : TW_SERVICE_HOST
228
+ end
229
+
230
+ # Source url of TransferwiseBank
231
+ # @return [String] the remote API url
232
+ def source_url
233
+ raise NoAccessKey if access_key.nil? || access_key.empty?
234
+ url_componenets = {
235
+ host: service_host,
236
+ path: TW_SERVICE_PATH,
237
+ query: "source=#{source}"
238
+ }
239
+ URI::HTTPS.build(url_componenets)
240
+ end
241
+
242
+ # Get rates expiration time based on ttl
243
+ # @return [Time] rates expiration time
244
+ def rates_expiration
245
+ rates_timestamp + ttl_in_seconds
246
+ end
247
+
248
+ # Get the timestamp of rates from first listed rate
249
+ # @return [Time] time object or nil
250
+ def rates_timestamp
251
+ raw = raw_rates_careful
252
+ raw.first.key?('time') ? Time.parse(raw.first['time']) : Time.at(0)
253
+ end
254
+
255
+ protected
256
+
257
+ # Store the provided text data by calling the proc method provided
258
+ # for the cache, or write to the cache file.
259
+ #
260
+ # @example
261
+ # store_in_cache("{\"quotes\": {\"USDAED\": 3.67304}}")
262
+ #
263
+ # @param text [String] parsed JSON content
264
+ # @return [String,Integer]
265
+ def store_in_cache(text)
266
+ if cache.is_a?(Proc)
267
+ cache.call(text)
268
+ elsif cache.is_a?(String) || cache.is_a?(Pathname)
269
+ write_to_file(text)
270
+ end
271
+ end
272
+
273
+ # Writes content to file cache
274
+ # @param text [String] parsed JSON content
275
+ # @return [String,Integer]
276
+ def write_to_file(text)
277
+ open(cache, 'w') do |f|
278
+ f.write(text)
279
+ end
280
+ rescue Errno::ENOENT
281
+ raise InvalidCache
282
+ end
283
+
284
+ # Read from cache when exist
285
+ # @return [Proc,String] parsed JSON content
286
+ def read_from_cache
287
+ if cache.is_a?(Proc)
288
+ cache.call(nil)
289
+ elsif (cache.is_a?(String) || cache.is_a?(Pathname)) &&
290
+ File.exist?(cache)
291
+ open(cache).read
292
+ end
293
+ end
294
+
295
+ # Get remote content and store in cache
296
+ # @return [String] unparsed JSON content
297
+ def read_from_url
298
+ rates = source_url.is_a?(URI::HTTPS) ? retrieve_rates : open_file
299
+ store_in_cache(rates) if valid_rates?(rates) && cache
300
+ rates
301
+ end
302
+
303
+ # Opens an file and reads the content
304
+ # @return [String] unparsed JSON content
305
+ def open_file
306
+ open(source_url).read
307
+ rescue OpenURI::HTTPError
308
+ ''
309
+ end
310
+
311
+ # Opens an url and reads the content
312
+ # @return [String] unparsed JSON content
313
+ def retrieve_rates
314
+ response = HTTParty.get(
315
+ source_url,
316
+ headers: { 'Authorization' => "Bearer #{access_key}" },
317
+ ssl_version: service_ssl_version
318
+ )
319
+ response.body
320
+ rescue HTTParty::Error, SocketError => e
321
+ raise e if raise_on_failure
322
+ [{}]
323
+ end
324
+
325
+ # Check validity of rates response only for store in cache
326
+ #
327
+ # @example
328
+ # valid_rates?("{\"quotes\": {\"USDAED\": 3.67304}}")
329
+ #
330
+ # @param [String] text is JSON content
331
+ # @return [Boolean] valid or not
332
+ def valid_rates?(text)
333
+ parsed = JSON.parse(text)
334
+ parsed && parsed.is_a?(Array) && !parsed.first.empty?
335
+ rescue JSON::ParserError, TypeError
336
+ false
337
+ end
338
+
339
+ # Get exchange rates with different strategies
340
+ #
341
+ # @example
342
+ # exchange_rates(true)
343
+ # exchange_rates
344
+ #
345
+ # @param straight [Boolean] true for straight, default is careful
346
+ # @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
347
+ def exchange_rates(straight = false)
348
+ @rates = if straight
349
+ raw_rates_straight
350
+ else
351
+ raw_rates_careful
352
+ end
353
+ end
354
+
355
+ # Get raw exchange rates from cache and then from url
356
+ # @param rescue_straight [Boolean] true for rescue straight, default true
357
+ # @return [String] JSON content
358
+ def raw_rates_careful(rescue_straight = true)
359
+ JSON.parse(read_from_cache.to_s)
360
+ rescue JSON::ParserError, TypeError
361
+ rescue_straight ? raw_rates_straight : [{}]
362
+ end
363
+
364
+ # Get raw exchange rates from url
365
+ # @return [String] JSON content
366
+ def raw_rates_straight
367
+ JSON.parse(read_from_url)
368
+ rescue JSON::ParserError, TypeError
369
+ raw_rates_careful(false)
370
+ end
371
+
372
+ # Get rate or calculate it as inverse rate
373
+ # @param [String] from_currency Currency ISO code. ex. 'USD'
374
+ # @param [String] to_currency Currency ISO code. ex. 'CAD'
375
+ # @return [Numeric] rate or rate calculated as inverse rate.
376
+ def get_rate_or_calc_inverse(from_currency, to_currency, opts = {})
377
+ rate = super_get_rate(from_currency, to_currency, opts)
378
+ unless rate
379
+ # Tries to calculate an inverse rate
380
+ inverse_rate = super_get_rate(to_currency, from_currency, opts)
381
+ if inverse_rate
382
+ rate = 1.0 / inverse_rate
383
+ add_rate(from_currency, to_currency, rate)
384
+ end
385
+ end
386
+ rate
387
+ end
388
+
389
+ # Tries to calculate a pair rate using base currency rate
390
+ # @param [String] from_currency Currency ISO code. ex. 'USD'
391
+ # @param [String] to_currency Currency ISO code. ex. 'CAD'
392
+ # @return [Numeric] rate or nil if cannot calculate rate.
393
+ def calc_pair_rate_using_base(from_currency, to_currency, opts = {})
394
+ from_base_rate = get_rate_or_calc_inverse(source, from_currency, opts)
395
+ to_base_rate = get_rate_or_calc_inverse(source, to_currency, opts)
396
+ if to_base_rate && from_base_rate
397
+ rate = to_base_rate / from_base_rate
398
+ add_rate(from_currency, to_currency, rate)
399
+ return rate
400
+ end
401
+ nil
402
+ end
403
+ end
404
+ end
405
+ end