# Add currency exchange rates and conversion to Ruby on Rails

Use Net::HTTP and Rails.cache. Cache current rates until nextUpdateAt and keep the key in server environment or credentials.

## Get a free API key

Sign up at https://currencyexchangerate-api.com/auth/login, then create a key under API keys in the console. The free plan includes 3,000 requests per month with no credit card. Store the secret as CXR_API_KEY in server-side environment configuration; never expose it in browser code.

File: `.env`

```shell
CXR_API_KEY=cxr_your_secret_key
```

## Create a reusable client and cache rates

Use the standard HTTP client. Cache every /latest response until nextUpdateAt: rates change at most once by then, and reusing the response saves quota. The helper also exposes /convert.

File: `app/services/rates.rb`

```ruby
require 'net/http'
require 'json'
require 'time'

class Rates
  API_BASE_URL = 'https://api.currencyexchangerate-api.com/api/v1'

  class Error < StandardError
    attr_reader :status, :retry_after

    def initialize(status, retry_after)
      @status = status
      @retry_after = retry_after
      super("Rates API status #{status}")
    end
  end

  def self.get(path)
    uri = URI(API_BASE_URL + path)
    headers = { 'Authorization' => 'Bearer ' + ENV.fetch('CXR_API_KEY') }
    request = Net::HTTP::Get.new(uri, headers)
    response = Net::HTTP.start(
      uri.hostname,
      uri.port,
      use_ssl: uri.scheme == 'https'
    ) { |http| http.request(request) }
    unless response.is_a?(Net::HTTPSuccess)
      raise Error.new(response.code.to_i, response['Retry-After'])
    end

    JSON.parse(response.body)
  end

  def self.latest(base = 'USD', symbols = 'EUR,GBP')
    key = "rates/#{base}/#{symbols}"
    cached = Rails.cache.read(key)
    return cached if cached

    query = URI.encode_www_form(base: base, symbols: symbols)
    data = get('/latest?' + query)
    Rails.cache.write(key, data, expires_at: Time.iso8601(data['nextUpdateAt']))
    data
  end

  def self.convert(from, to, amount)
    query = URI.encode_www_form(from: from, to: to, amount: amount)
    get('/convert?' + query)
  end
end
```

## Use it in Ruby on Rails

Call the helper from a server route, controller, view, command, or script. Format converted values for the user’s locale.

File: `app/controllers/conversions_controller.rb`

```ruby
data = Rates.convert('EUR', 'GBP', 100)
data['formatted'] = helpers.number_to_currency(data['result'], unit: '€')
render json: data
```

## Handle API errors

A 401 means the key is invalid. A 429 can mean a rate limit or quota limit; read Retry-After in seconds before retrying. Other errors use application/problem+json.

File: `app/controllers/conversions_controller.rb`

```ruby
begin
  data = Rates.convert('EUR', 'GBP', 100)
rescue Rates::Error => e
  response.set_header('Retry-After', e.retry_after) if e.status == 429
  render json: { error: e.message }, status: e.status == 429 ? 429 : 502
end
```

## Format the amount for display

Use locale-aware currency formatting instead of adding a symbol manually.

File: `format.rb`

```ruby
helpers.number_to_currency(
  85.249105,
  unit: '€',
  separator: ',',
  delimiter: '.',
  format: '%n %u'
)
```

## Related links

- [API documentation](https://currencyexchangerate-api.com/docs.md)
- [All integration guides](https://currencyexchangerate-api.com/guides.md)
- [Get a free API key](https://currencyexchangerate-api.com/auth/login)
