123 lines
2.9 KiB
Ruby
123 lines
2.9 KiB
Ruby
|
|
# 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
|