Created
December 5, 2023 22:06
-
-
Save herko/af4286240f470c96c8e5f2c3d9fd258f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is an ExchangeRate model class. It is responsible for keeping local exchange rates up to date. | |
# Cron job trigger `ExchangeRate.update` to periodically refresh rates. This method also verifies if `auto` update is enabled. This gives admin ability to temporary disable refreshing. | |
# The update method fetches refresh rates and creates either direct conversion between two currencies, or indirect (through 3rd currency) if its not available. | |
# This implementation works as is, but can be better. Please refactor this class to extract and separate responsibilities and overall improve code quality. | |
require 'money/bank/open_exchange_rates_bank' | |
class ExchangeRate < ActiveRecord::Base | |
CURRENCIES = %w[usd eur aed] # and more | |
class << self | |
def oxr_bank | |
oxr = Money::Bank::OpenExchangeRatesBank.new | |
oxr.ttl_in_seconds = 86400 | |
oxr.app_id = ENV['EXCHANGE_RATES_API_KEY'] | |
oxr | |
end | |
def update_rates | |
return unless auto? | |
oxr_response = oxr_bank.refresh_rates | |
oxr_rates = JSON.parse(oxr_response) | |
base_currency = oxr_rates['base'] | |
rates = oxr_rates['rates'] | |
CURRENCIES.combination(2).each do |currency_a, currency_b| | |
if currency_a == base_currency | |
add_direct_rate( | |
rates: rates, | |
base_currency: currency_a, | |
currency: currency_b | |
) | |
elsif currency_b == base_currency | |
add_direct_rate( | |
rates: rates, | |
base_currency: currency_b, | |
currency: currency_a | |
) | |
else | |
add_indirect_rate( | |
rates: rates, | |
currency_a: currency_a, | |
currency_b: currency_b | |
) | |
end | |
end | |
end | |
def add_direct_rate(rates:, base_currency:, currency:) | |
ExchangeRate.add_rate(base_currency, currency, rates[currency].to_f) | |
ExchangeRate.add_rate(currency, base_currency, 1/rates[currency].to_f) | |
end | |
def add_indirect_rate(rates:, currency_a:, currency_b:) | |
a_to_base = 1/rates[currency_a].to_f | |
base_to_b = rates[currency_b].to_f | |
rate = a_to_base * base_to_b | |
ExchangeRate.add_rate(currency_a, currency_b, rate) | |
ExchangeRate.add_rate(currency_b, currency_a, 1/rate) | |
end | |
end | |
validates :from, presence: true | |
validates :to, presence: true | |
validates :rate, presence: true | |
validates :rate, numericality: {greater_than: 0.0} | |
def self.auto? | |
Redis.current.exists('exchange_rates_auto') && Redis.current.get('exchange_rates_auto') == '1' | |
end | |
def self.auto=(value) | |
Redis.current.set('exchange_rates_auto', value) | |
end | |
def self.usd_rates | |
where(from: 'USD', to: CURRENCIES).order("created_at asc") | |
end | |
def self.get_rate(from_iso_code, to_iso_code) | |
@@rates_cache ||= {} | |
@@rates_cache["#{from_iso_code}-#{to_iso_code}"] ||= find_by(from: from_iso_code, to: to_iso_code).try(:rate) | |
end | |
def self.add_rate(from_iso_code, to_iso_code, rate) | |
ExchangeRateHistory.create(from: from_iso_code, to: to_iso_code, rate: rate) | |
currency = find_or_initialize_by(from: from_iso_code, to: to_iso_code) | |
currency.rate = rate | |
currency.save! | |
reset_cache | |
end | |
def self.reset_cache | |
@@rates_cache = {} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment