Skip to content

Commit

Permalink
Add force_refresh_rate_on_expire option and use api timestamp
Browse files Browse the repository at this point in the history
ref #38
ref #47
ref #48
  • Loading branch information
spk committed Mar 31, 2018
1 parent 6170679 commit 0519b1a
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 60 deletions.
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,56 @@ gem install money-open-exchange-rates

~~~ ruby
require 'money/bank/open_exchange_rates_bank'
# memory store per default; for others just pass as argument a class like

# Memory store per default; for others just pass as argument a class like
# explained in https://github.com/RubyMoney/money#exchange-rate-stores
oxr = Money::Bank::OpenExchangeRatesBank.new
# see https://github.com/spk/money-open-exchange-rates#cache for more info
oxr.cache = 'path/to/file/cache.json'
oxr.app_id = 'your app id from https://openexchangerates.org/signup'
oxr.update_rates

# (optional)
# set the seconds after than the current rates are automatically expired
# See https://github.com/spk/money-open-exchange-rates#cache for more info
# Updated only when `refresh_rates` is called
oxr.cache = 'path/to/file/cache.json'

# (optional)
# Set the seconds after than the current rates are automatically expired
# by default, they never expire, in this example 1 day.
# This ttl is about money store (memory, database ...) passed though
# `Money::Bank::OpenExchangeRatesBank` as argument not about `cache` option.
# The base time is the timestamp fetched from API.
oxr.ttl_in_seconds = 86400

# (optional)
# set historical date of the rate
# Set historical date of the rate
# see https://openexchangerates.org/documentation#historical-data
oxr.date = '2015-01-01'

# (optional)
# Set the base currency for all rates. By default, USD is used.
# OpenExchangeRates only allows USD as base currency
# for the free plan users.
oxr.source = 'USD'

# (optional)
# Extend returned values with alternative, black market and digital currency
# rates. By default, false is used
# see: https://docs.openexchangerates.org/docs/alternative-currencies
oxr.show_alternative = true

# (optional)
# Store in cache
# Force rates storage in cache, this is done automaticly after TTL is expire.
# Refresh rates, store in cache and update rates
# Should be used on crontab/worker/scheduler `Money.default_bank.refresh_rates`
# If you are using unicorn-worker-killer gem or on Heroku like platform,
# you should avoid to put this on the initializer of your Rails application,
# because will increase your OXR API usage.
oxr.save_rates
oxr.refresh_rates

# (optional)
# Force refresh rates cache and store on the fly when ttl is expired
# This will slow down request on get_rate, so use at your on risk, if you don't
# want to setup crontab/worker/scheduler for your application
oxr.force_refresh_rate_on_expire = true

Money.default_bank = oxr

Expand Down Expand Up @@ -113,16 +130,25 @@ oxr.cache = Proc.new do |text|
end
~~~

To update the cache call `Money.default_bank.refresh_rates` on
crontab/worker/scheduler. This have to be done this way because the fetch can
take some time (HTTP call) and can fail.

## Full example configuration initializer with Rails and cache

~~~ ruby
require 'money/bank/open_exchange_rates_bank'

OXR_CACHE_KEY = 'money:exchange_rates'.freeze
oxr = Money::Bank::OpenExchangeRatesBank.new
# ExchangeRate is an ActiveRecord model
# more info at https://github.com/RubyMoney/money#exchange-rate-stores
oxr = Money::Bank::OpenExchangeRatesBank.new(ExchangeRate)
oxr.ttl_in_seconds = 86400
oxr.cache = Proc.new do |text|
if text
# only expire when refresh_rates is called or `force_refresh_rate_on_expire`
# option is enabled
# you can also set `expires_in` option on write to force fetch new rates
Rails.cache.write(OXR_CACHE_KEY, text)
else
Rails.cache.read(OXR_CACHE_KEY)
Expand Down
65 changes: 49 additions & 16 deletions lib/money/bank/open_exchange_rates_bank.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class OpenExchangeRatesBank < Money::Bank::VariableExchange

# Default base currency "base": "USD"
OE_SOURCE = 'USD'.freeze
RATES_KEY = 'rates'.freeze
TIMESTAMP_KEY = 'timestamp'.freeze

# @deprecated secure_connection is deprecated and has no effect
def secure_connection=(*)
Expand Down Expand Up @@ -64,6 +66,13 @@ def secure_connection=(*)
# @return [String] The requested date in YYYY-MM-DD format
attr_accessor :date

# Force refresh rates cache and store on the fly when ttl is expired
# This will slow down request on get_rate, so use at your on risk, if you
# don't want to setup crontab/worker/scheduler for your application
#
# @param [Boolean]
attr_accessor :force_refresh_rate_on_expire

# Rates expiration Time
#
# @return [Time] expiration time
Expand Down Expand Up @@ -93,6 +102,20 @@ def secure_connection=(*)
# @return [Boolean] Setted show alternative
attr_writer :show_alternative

# Set current rates timestamp
#
# @return [Time]
def rates_timestamp=(at)
@rates_timestamp = Time.at(at)
end

# Current rates timestamp
#
# @return [Time]
def rates_timestamp
@rates_timestamp || Time.at(0)
end

# Set the seconds after than the current rates are automatically expired
# by default, they never expire.
#
Expand Down Expand Up @@ -144,17 +167,6 @@ def update_rates
end
end

# Save rates on cache
# Can raise InvalidCache
#
# @return [Proc,File]
def save_rates
return nil unless cache
store_in_cache(@json_response) if valid_rates?(@json_response)
rescue Errno::ENOENT
raise InvalidCache
end

# Alias super method
alias super_get_rate get_rate

Expand All @@ -171,13 +183,23 @@ def get_rate(from_currency, to_currency, opts = {})
rate || calc_pair_rate_using_base(from_currency, to_currency, opts)
end

# Fetch from url and save cache
#
# @return [Array] Array of exchange rates
def refresh_rates
read_from_url
end

# Alias refresh_rates method
alias save_rates refresh_rates

# Expire rates when expired
#
# @return [NilClass, Time] nil if not expired or new expiration time
def expire_rates
return unless ttl_in_seconds
return if rates_expiration > Time.now
read_from_url
refresh_rates if force_refresh_rate_on_expire
update_rates
refresh_rates_expiration
end
Expand Down Expand Up @@ -205,6 +227,16 @@ def source_url

protected

# Save rates on cache
# Can raise InvalidCache
#
# @return [Proc,File]
def save_cache
store_in_cache(@json_response) if valid_rates?(@json_response)
rescue Errno::ENOENT
raise InvalidCache
end

# Latest url if no date given
#
# @return [String] URL
Expand Down Expand Up @@ -266,7 +298,7 @@ def read_from_cache
def read_from_url
raise NoAppId if app_id.nil? || app_id.empty?
@json_response = open(source_url).read
save_rates
save_cache if cache
@json_response
end

Expand All @@ -280,7 +312,7 @@ def read_from_url
def valid_rates?(text)
return false unless text
parsed = JSON.parse(text)
parsed && parsed.key?('rates')
parsed && parsed.key?(RATES_KEY) && parsed.key?(TIMESTAMP_KEY)
rescue JSON::ParserError
false
end
Expand All @@ -290,14 +322,15 @@ def valid_rates?(text)
# @return [Hash] key is country code (ISO 3166-1 alpha-3) value Float
def exchange_rates
doc = JSON.parse(read_from_cache || read_from_url)
@oer_rates = doc['rates']
self.rates_timestamp = doc[TIMESTAMP_KEY]
@oer_rates = doc[RATES_KEY]
end

# Refresh expiration from now
#
# @return [Time] new expiration time
def refresh_rates_expiration
@rates_expiration = Time.now + ttl_in_seconds
@rates_expiration = rates_timestamp + ttl_in_seconds
end

# Get rate or calculate it as inverse rate
Expand Down
Loading

0 comments on commit 0519b1a

Please sign in to comment.