rails caching with money gem (eu_central_bank)
I found this add-on for the Money gem which updates from the ECB European Central Bank (updates its rates every 24 hours) but I'm unsure how I should go about caching in my rails app which uses multiple currencies.
http://github.com/RubyMoney/eu_central_bank
eu_bank ||= EuCentralBank.new
eu_bank.update_rates
#Rails.cache.fetch('rates', :expires_in => 24.hours) { eu_bank.update_rates }
rate = eu_bank.exchange_with(Money.new(100, session[:currency]), "USD").to_f
It has a function to write out the rates to some file... but i'm 开发者_Python百科not sure that's what I want either. I'm also using heroku which has a read-only file system.
eu_bank.save_rates("/some/file/location/exchange_rates.xml")
I couldn't find any way to check the age on the object either. I'm just wondering the best option to load it once per 24 hours and persist for my entire Rails app. Any pointers?
This can be done by using Rails low-level caching and a before_filter:
class ApplicationController < ActionController::Base
before_filter :set_conversion_rates
def set_conversion_rates
rates = Rails.cache.fetch "money:eu_central_bank_rates", expires_in: 24.hours do
Money.default_bank.save_rates_to_s
end
Money.default_bank.update_rates_from_s rates
end
end
This code will download the rates once in 24 hours and save the results to the cache (whatever caching module you use), from where the bank object loads them on every request.
My current solution is not very elegant, but works at the moment - I'm using initializer and update/cache rates on app start.
config/initializers/money.rb
# Money exchange
::Money.default_bank = ::EuCentralBank.new
EU_CENTRAL_BANK_CACHE = '/tmp/eu_bank_exchange_rates.xml'
# Right now we update exchange rates on app restart. App is restarted daily after logs rotation,
# Should be ok for now.
if (!File.exist?(EU_CENTRAL_BANK_CACHE)) || File.mtime(EU_CENTRAL_BANK_CACHE) < 23.hours.ago
p "Updating money exchange rates"
::Money.default_bank.save_rates(EU_CENTRAL_BANK_CACHE)
end
::Money.default_bank.update_rates(EU_CENTRAL_BANK_CACHE)
I just deployed a solution that caches the exchange rates in memcache and updates it every 24 hours. You have to use the latest money gem, commit https://github.com/RubyMoney/eu_central_bank/commit/fc6c4a3164ad47747c8abbf5c21df617d2d9e644 is required. Since I don't want to restart my web processes every 24 hours, I check for new exchange rates in a before_filter
(nicer way possible?). The actual Money.default_bank.update_rates
call might be moved into a recurring resque job (or whatever).
lib/my_namespace/bank.rb
module MyNamespace
class Bank
def self.update_rates_if_changed
if last_updated.nil? || last_updated + 12.hours <= Time.now
update_rates
end
end
def self.update_rates
fetch_rates
# Take latest available currency rates snapshot
[0, 1, 2].each do |days|
date = Date.today - days.days
next unless Rails.cache.exist?(rate_key(date))
begin
rates = Rails.cache.read(rate_key(date))
Money.default_bank.update_rates_from_s(rates)
rescue Nokogiri::XML::SyntaxError
print "error occurred while reading currency rates"
# our rates seem to be invalid, so clear the cache and retry
Rails.cache.delete(rate_key(date))
update_rates
end
break
end
end
private
def self.fetch_rates
return if Rails.cache.exist?(rate_key)
print "Updating currency rates ... "
begin
Rails.cache.write(rate_key, Money.default_bank.save_rates_to_s)
puts "finished"
rescue Exception => ex
puts "error occurred: #{ex.inspect}"
end
end
def self.rate_key(date = Date.today)
["exchange_rates", date.strftime("%Y%m%d")]
end
def self.last_updated
Money.default_bank.last_updated
end
end
end
app/controllers/application_controller.rb
class ApplicationController
before_filter :check_for_currency_updates
def check_for_currency_updates
MyNamespace::Bank.update_rates_if_changed
end
end
config/initializers/money.rb
MyNamespace::Bank.update_rates_if_changed
Since the amount of data is relatively small you can Marshal.dump the eu_bank object, store it in memchache with an expiry date of 24 hours (see expiration in this doc).
And each time you need it you retrieve it from memchache and Marshal.load it.
If the key has expired or vanished from the cache, you fetch it again for real
精彩评论