Class: ExchangeRate

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/exchange_rate.rb

Overview

== Schema Information

Table name: exchange_rates
Database name: primary

id :integer not null, primary key
effective_date :date
from_currency :string(255)
rate :decimal(8, 6)
to_currency :string(255)
created_at :datetime
updated_at :datetime

Indexes

idx_unique_exchange_rate (from_currency,to_currency,effective_date) UNIQUE

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Class Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.add_rate(from_currency, to_currency, rate) ⇒ Object

For the money interface so this store can beused by Money::Bank::VariableExchange



29
30
31
# File 'app/models/exchange_rate.rb', line 29

def self.add_rate(from_currency, to_currency, rate)
  create_with(rate: rate).find_or_create_by(from_currency: from_currency, to_currency: to_currency, effective_date: Date.current)
end

.convert_currency(from_currency, to_currency, date, amount) ⇒ BigDecimal

Convert amount from from_currency to to_currency at the
FX rate effective on date, rounded to 2 decimals.

Parameters:

  • from_currency (String)

    ISO 4217 code

  • to_currency (String)
  • date (Date)
  • amount (Numeric)

Returns:

  • (BigDecimal)


50
51
52
53
# File 'app/models/exchange_rate.rb', line 50

def self.convert_currency(from_currency, to_currency, date, amount)
  conversion_rate = download_rate(from_currency, to_currency, date)
  (amount * conversion_rate.rate).round(2)
end

.download_rate(from_currency, to_currency, effective_date = nil) ⇒ ExchangeRate?

Find or create the ExchangeRate record for the
(from, to, date) triple. On a miss, fetches the rate from
OpenExchangeRatesClient and caches both directions
(forward + inverse) so subsequent lookups are local.
effective_date is clamped to today so future-dated requests
don't poison the cache with bad data.

Parameters:

  • from_currency (String)
  • to_currency (String)
  • effective_date (Date, nil) (defaults to: nil)

Returns:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'app/models/exchange_rate.rb', line 78

def self.download_rate(from_currency, to_currency, effective_date = nil)
  effective_date = Date.current if effective_date.nil? || effective_date > Date.current
  rate = find_by(from_currency: from_currency, to_currency: to_currency, effective_date: effective_date)
  if rate.nil?
    # On a cache miss, fetch the historical rate from Open Exchange Rates.
    ex_rate = OpenExchangeRatesClient.new.rate(effective_date, from_currency, to_currency)
    return nil if ex_rate.nil?

    logger.info "Getting rate for #{effective_date} (#{from_currency} to #{to_currency})"
    rate = ExchangeRate.create!(from_currency: from_currency, to_currency: to_currency, rate: ex_rate, effective_date: effective_date)
    inverse_rate = 1.0 / ex_rate
    create_with(rate: inverse_rate).find_or_create_by(from_currency: to_currency, to_currency: from_currency, effective_date: effective_date)
  end
  rate
end

.each_rateObject

For the money interface so this store can beused by Money::Bank::VariableExchange



34
35
36
37
38
39
40
# File 'app/models/exchange_rate.rb', line 34

def self.each_rate
  return find_each unless block_given?

  find_each do |rate|
    yield rate.from_currency, rate.to_currency, rate.rate
  end
end

.get_all_exchange_rates(logger = Rails.logger) ⇒ void

This method returns an undefined value.

Backfill / catch-up: for every day so far this calendar year
(and the entire current month for non-USD companies),
cache USD↔company-currency rates. Run by the daily ops job.

Parameters:

  • logger (Logger) (defaults to: Rails.logger)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/exchange_rate.rb', line 100

def self.get_all_exchange_rates(logger = Rails.logger)
  days = (1..Date.current.day)
  days.each do |day|
    date = Date.new(Date.current.year, Date.current.month, day)
    logger.info "Getting day rates for #{date}"
    companies = Company.where.not(id: 3)
    companies.each do |company|
      ExchangeRate.download_rate('USD', company.currency, date)
      ExchangeRate.download_rate(company.currency, 'USD', date)
    end
  end

  return if Date.current.month == 1

  months = (1..(Date.current.month - 1))
  months.each do |month|
    days = (1..Time.days_in_month(month, Date.current.year))
    days.each do |day|
      date = Date.new(Date.current.year, month, day)
      logger.info "Getting month rates for #{date}"
      companies = Company.where.not(id: [1, 3])
      companies.each do |company|
        ExchangeRate.download_rate('USD', company.currency, date)
        ExchangeRate.download_rate(company.currency, 'USD', date)
      end
    end
  end
end

.get_exchange_rate(from_currency, to_currency, date = nil) ⇒ BigDecimal?

Numeric rate from from_currencyto_currency on date
(defaults to today). Returns nil only when the Open Exchange
Rates lookup also fails.

Parameters:

  • from_currency (String)
  • to_currency (String)
  • date (Date, nil) (defaults to: nil)

Returns:

  • (BigDecimal, nil)


63
64
65
# File 'app/models/exchange_rate.rb', line 63

def self.get_exchange_rate(from_currency, to_currency, date = nil)
  download_rate(from_currency, to_currency, date)&.rate
end

.get_exchange_rates_for_today(_logger = Rails.logger) ⇒ void

This method returns an undefined value.

Just-today version of get_all_exchange_rates. Skips US
(Company::USA) and the placeholder company id 3 since their
functional currency is already USD.

Parameters:

  • _logger (Logger) (defaults to: Rails.logger)


135
136
137
138
139
140
141
# File 'app/models/exchange_rate.rb', line 135

def self.get_exchange_rates_for_today(_logger = Rails.logger)
  companies = Company.where.not(id: [1, 3])
  companies.each do |company|
    ExchangeRate.download_rate('USD', company.currency, Date.current)
    ExchangeRate.download_rate(company.currency, 'USD', Date.current)
  end
end

.get_rate(from_currency, to_currency) ⇒ Object

For the money interface so this store can beused by Money::Bank::VariableExchange



24
25
26
# File 'app/models/exchange_rate.rb', line 24

def self.get_rate(from_currency, to_currency)
  get_exchange_rate(from_currency, to_currency)
end