Class: Money::Bank::UniRate

Inherits:
VariableExchange
  • Object
show all
Defined in:
lib/money/bank/uni_rate.rb

Overview

A Money::Bank implementation backed by the UniRate API (unirateapi.com).

It fetches a single-base rate snapshot from ‘GET /api/rates` and derives every cross-rate from it on demand, so one HTTP call covers all currency pairs. Derived cross-rates are computed per call (not cached in the store) so a TTL refresh can never serve a stale pair.

require "money/bank/uni_rate"

Money.default_bank = Money::Bank::UniRate.new(api_key: ENV["UNIRATE_API_KEY"])
Money.new(100_00, "USD").exchange_to("EUR")  # => #<Money fractional:9200 currency:EUR>

Rates are fetched lazily on the first conversion and re-fetched once ttl_in_seconds has elapsed. Leave ttl_in_seconds as nil to fetch exactly once and cache for the life of the object; call #flush_rates to force a refresh on the next conversion.

Constant Summary collapse

DEFAULT_BASE_URL =
"https://api.unirateapi.com"
DEFAULT_BASE_CURRENCY =
"USD"
DEFAULT_TIMEOUT =
30

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: ENV.fetch("UNIRATE_API_KEY", nil), base_currency: DEFAULT_BASE_CURRENCY, ttl_in_seconds: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, store: Money::RatesStore::Memory.new, &block) ⇒ UniRate

Returns a new instance of UniRate.

Parameters:

  • api_key (String) (defaults to: ENV.fetch("UNIRATE_API_KEY", nil))

    UniRate API key (falls back to ENV)

  • base_currency (String) (defaults to: DEFAULT_BASE_CURRENCY)

    currency the snapshot is fetched against

  • ttl_in_seconds (Integer, nil) (defaults to: nil)

    re-fetch interval; nil = fetch once

  • base_url (String) (defaults to: DEFAULT_BASE_URL)

    base URL override (for testing)

  • timeout (Integer, Float) (defaults to: DEFAULT_TIMEOUT)

    per-request timeout in seconds

  • store (Money::RatesStore::Memory) (defaults to: Money::RatesStore::Memory.new)

    rate store (defaults to in-memory)



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/money/bank/uni_rate.rb', line 48

def initialize(api_key: ENV.fetch("UNIRATE_API_KEY", nil),
               base_currency: DEFAULT_BASE_CURRENCY,
               ttl_in_seconds: nil,
               base_url: DEFAULT_BASE_URL,
               timeout: DEFAULT_TIMEOUT,
               store: Money::RatesStore::Memory.new,
               &block)
  if api_key.nil? || api_key.to_s.empty?
    raise ArgumentError, "api_key is required (pass api_key: or set UNIRATE_API_KEY)"
  end

  @api_key = api_key
  @base_currency = base_currency.to_s.upcase
  @ttl_in_seconds = ttl_in_seconds
  @base_url = base_url
  @timeout = timeout
  @rates_updated_at = nil
  super(store, &block)
end

Instance Attribute Details

#base_currencyObject (readonly)

Returns the value of attribute base_currency.



39
40
41
# File 'lib/money/bank/uni_rate.rb', line 39

def base_currency
  @base_currency
end

#rates_updated_atObject (readonly)

Returns the value of attribute rates_updated_at.



39
40
41
# File 'lib/money/bank/uni_rate.rb', line 39

def rates_updated_at
  @rates_updated_at
end

#ttl_in_secondsObject

Returns the value of attribute ttl_in_seconds.



40
41
42
# File 'lib/money/bank/uni_rate.rb', line 40

def ttl_in_seconds
  @ttl_in_seconds
end

Instance Method Details

#expired?Boolean

True when rates have never been fetched, or ttl_in_seconds elapsed.

Returns:

  • (Boolean)


80
81
82
83
84
85
# File 'lib/money/bank/uni_rate.rb', line 80

def expired?
  return true if rates_updated_at.nil?
  return false if ttl_in_seconds.nil?

  Time.now - rates_updated_at > ttl_in_seconds
end

#flush_ratesObject

Force a refresh on the next conversion (does not hit the network now).



102
103
104
105
# File 'lib/money/bank/uni_rate.rb', line 102

def flush_rates
  @rates_updated_at = nil
  self
end

#get_rate(from, to, _opts = {}) ⇒ BigDecimal

Exchange rate from from to to, deriving cross-rates from the single-base snapshot. Auto-refreshes when #expired?.

Returns:

  • (BigDecimal)


91
92
93
94
95
96
97
98
99
# File 'lib/money/bank/uni_rate.rb', line 91

def get_rate(from, to, _opts = {})
  from_iso = Money::Currency.wrap(from).iso_code
  to_iso = Money::Currency.wrap(to).iso_code

  update_rates if expired?
  return BigDecimal(1) if from_iso == to_iso

  base_rate(to_iso) / base_rate(from_iso)
end

#update_ratesObject

Fetch the latest snapshot and store base->currency rates. Returns the raw { “EUR” => BigDecimal, … } map. Called lazily on the first conversion; safe to call manually to warm the cache.



71
72
73
74
75
76
77
# File 'lib/money/bank/uni_rate.rb', line 71

def update_rates
  rates = fetch_rates
  add_rate(base_currency, base_currency, 1)
  rates.each { |code, value| add_rate(base_currency, code, value) }
  @rates_updated_at = Time.now
  rates
end