frankfurter/lib/currency.rb

123 lines
2.9 KiB
Ruby
Raw Normal View History

2026-05-21 01:38:14 -06:00
# frozen_string_literal: true
require "currency_coverage"
require "money/currency"
require "peg"
# Model backed by the materialized currencies table. Populated incrementally
# during Provider#backfill. Derives one row per currency with date range.
class Currency < Sequel::Model(:currencies)
unrestrict_primary_key
one_to_many :currency_coverages, key: :iso_code
many_to_many :providers, join_table: :currency_coverages, left_key: :iso_code, right_key: :provider_key
dataset_module do
def active
cutoff = (Date.today - 30).to_s
where { end_date >= cutoff }
end
def with_providers(keys)
iso_codes = CurrencyCoverage.where(provider_key: keys)
.select(:iso_code).distinct
where(iso_code: iso_codes)
end
end
class << self
def all
merge_pegged(super)
end
def active
merge_pegged(super.all)
end
def map(&block)
all.map(&block)
end
def find(code)
code = code.upcase
peg = Peg.find(code)
db_record = where(iso_code: code).first
if db_record
apply_peg(db_record, peg, where(iso_code: peg.base).first) if peg
db_record
elsif peg
anchor = where(iso_code: peg.base).first
return unless anchor
new_pegged(peg, anchor)
end
end
private
def merge_pegged(db_currencies)
anchors = db_currencies.to_h { |c| [c.iso_code, c] }
pegged = Peg.all.filter_map do |peg|
existing = anchors[peg.quote]
if existing
apply_peg(existing, peg, anchors[peg.base])
nil
else
anchor = anchors[peg.base]
next unless anchor
new_pegged(peg, anchor)
end
end
(db_currencies + pegged).sort_by(&:iso_code)
end
def apply_peg(record, peg, anchor)
record.instance_variable_set(:@peg, peg)
return unless anchor
peg_start = [peg.since, Date.parse(anchor.start_date.to_s)].compact.max.to_s
record.start_date = peg_start if peg_start < record.start_date.to_s
record.end_date = anchor.end_date if anchor.end_date.to_s > record.end_date.to_s
end
def new_pegged(peg, anchor)
c = new(iso_code: peg.quote, start_date: anchor.start_date, end_date: anchor.end_date)
apply_peg(c, peg, anchor)
c
end
end
attr_reader :peg
def metadata
@metadata ||= Money::Currency.find(iso_code)
end
def to_h
{
iso_code: iso_code,
iso_numeric: metadata&.iso_numeric,
name: metadata&.name || iso_code,
symbol: metadata&.symbol,
start_date: start_date.to_s,
end_date: end_date.to_s,
}
end
def providers
CurrencyCoverage.where(iso_code: iso_code)
.order(:provider_key)
.select_map(:provider_key)
end
def to_h_with_providers
h = to_h.merge(providers: providers)
h[:peg] = { base: peg.base, rate: peg.rate, authority: peg.authority } if peg
h
end
end