we-promise/sure · error · Money::ConversionError
Couldn't find exchange rate from #{from_currency} to #{to_cu
Error message
Couldn't find exchange rate from #{from_currency} to #{to_currency} on #{date} What it means
Money#exchange_to looks up a rate via store.find_or_fetch_rate (ExchangeRate) for the given date; if no rate is found - or the rate is nil/zero/negative - it raises Money::ConversionError carrying from_currency, to_currency and date. custom_rate, when supplied, bypasses the lookup entirely.
Source
Thrown at lib/money.rb:65
# Priority:
# 1. Use custom_rate if explicitly provided (not nil)
# 2. Look up rate via store.find_or_fetch_rate
# 3. Raise ConversionError if no valid rate available
def exchange_to(other_currency, date: Date.current, custom_rate: nil)
iso_code = currency.iso_code
other_iso_code = Money::Currency.new(other_currency).iso_code
if iso_code == other_iso_code
self
else
# Use custom rate if provided, otherwise look it up
if custom_rate.present?
exchange_rate = custom_rate.to_d
else
exchange_rate = store.find_or_fetch_rate(from: iso_code, to: other_iso_code, date: date)&.rate
end
raise ConversionError.new(from_currency: iso_code, to_currency: other_iso_code, date: date) unless exchange_rate && exchange_rate > 0
Money.new(amount * exchange_rate, other_iso_code)
end
end
def as_json
{ amount: amount, currency: currency.iso_code, formatted: format }.as_json
end
def <=>(other)
raise TypeError, "Money can only be compared with other Money objects except for 0" unless other.is_a?(Money) || other.eql?(0)
if other.is_a?(Numeric)
amount <=> other
else
amount_comparison = amount <=> other.amount
if amount_comparison == 0View on GitHub (pinned to e69894adb9)
Solutions
- Pass custom_rate when you already know the rate (e.g. a rate captured at transaction time)
- Backfill the missing pair/date via the ExchangeRateSynthesizer/job so cross rates get derived
- Fallback to the nearest earlier date's rate before giving up (many call sites rescue ConversionError and skip/defer)
- Check for zero/negative corrupt rows in exchange_rates - a stored 0 also triggers this raise
- For weekends/holidays, synthesize the rate from the previous business day
Example fix
# before
usd = money.exchange_to("USD")
# after - explicit rate, else nearest-date fallback
usd = money.exchange_to("USD", custom_rate: captured_rate) if captured_rate
usd ||= begin
money.exchange_to("USD", date: date)
rescue Money::ConversionError
money.exchange_to("USD", date: ExchangeRate.where(from_currency: money.currency.iso_code, to_currency: "USD").order(date: :desc).first&.date)
end Defensive patterns
Strategy: fallback
Validate before calling
rate = ExchangeRate.find_rate(from: from, to: to, date: date) rescue nil rate ||= ExchangeRateSynthesizer.derive(from: from, to: to, date: date) raise Money::ConversionError.new(from_currency: from, to_currency: to, date: date) unless rate&.positive_rate?
Type guard
def convertible?(money, to:, date: Date.current)
money.currency.iso_code == Money::Currency.new(to).iso_code ||
ExchangeRate.rate_exists?(from: money.currency.iso_code, to: to, date: date)
rescue Money::Currency::UnknownCurrency
false
end Try / catch
begin
money.exchange_to("USD", date: date)
rescue Money::ConversionError => e
Rails.logger.warn("Missing rate #{e.from_currency}->#{e.to_currency} on #{e.date}; using nearest")
nearest = ExchangeRate.latest_before(e.from_currency, e.to_currency, e.date)
nearest ? money.exchange_to("USD", custom_rate: nearest.rate) : skip_conversion(money)
end Prevention
- Always pass custom_rate when the business captured one at transaction time
- Run the exchange-rate synthesis job before net-worth/portfolio syncs
- Backfill weekend/holiday rates from the prior business day
- Alert on ConversionError frequency - spikes indicate the rate-fetch provider is failing
When it happens
Trigger: Converting a balance/transaction between currencies for a date with no stored or synthesizable rate: weekend/holiday dates where markets were closed; exotic pairs with no direct or cross rate (e.g. NZD to TRY); dates older than the exchange-rate history in the database; provider rate fetch disabled or failed so nothing was persisted.
Common situations: Portfolio net-worth syncing across many currencies where one minor pair lacks data; back-filling historical balances before the first ExchangeRate record; Yahoo/sync job failures leaving gaps in the rates table; custom date ranges in reports hitting sparse dates.
Related errors
- Realie did not return a valuation for this property.
- API error (code: #{error_code}): #{error_message}
- Failed to fetch exchange rates: #{rates_response.error.messa
- No exchange rate found for #{from}/#{to} on or before #{date
- No chart data found for currency pair #{from}/#{to}
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/cf2afa34112eded5.
Report an issue: GitHub.