mirror of
https://github.com/hakanensari/frankfurter.git
synced 2024-11-22 02:52:49 +01:00
Fold conversion into quote
This commit is contained in:
parent
1754efd0cb
commit
60f980a6ee
12
lib/api.rb
12
lib/api.rb
@ -4,7 +4,6 @@ require 'oj'
|
|||||||
require 'sinatra'
|
require 'sinatra'
|
||||||
require 'rack/cors'
|
require 'rack/cors'
|
||||||
require 'quote'
|
require 'quote'
|
||||||
require 'converter'
|
|
||||||
|
|
||||||
configure :development do
|
configure :development do
|
||||||
set :show_exceptions, :after_handler
|
set :show_exceptions, :after_handler
|
||||||
@ -32,12 +31,8 @@ helpers do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def converter
|
|
||||||
@converter ||= Converter.new(params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def symbols
|
def symbols
|
||||||
@symbols ||= params.values_at('symbols', 'currencies').first
|
@symbols ||= params.values_at('symbols', 'to').compact.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def jsonp(data)
|
def jsonp(data)
|
||||||
@ -83,11 +78,6 @@ get(/(?<date>\d{4}-\d{2}-\d{2})/) do
|
|||||||
jsonp quote_attributes
|
jsonp quote_attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/converter' do
|
|
||||||
params[:base] = params[:from]
|
|
||||||
jsonp Hash(amount: converter.convert(quote))
|
|
||||||
end
|
|
||||||
|
|
||||||
not_found do
|
not_found do
|
||||||
halt 404, encode_json(error: 'Not found')
|
halt 404, encode_json(error: 'Not found')
|
||||||
end
|
end
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'bigdecimal'
|
|
||||||
require 'quote'
|
|
||||||
|
|
||||||
class Converter
|
|
||||||
attr_reader :to, :from
|
|
||||||
|
|
||||||
def initialize(params = {})
|
|
||||||
@amount = params[:amount]
|
|
||||||
@from = params[:from]
|
|
||||||
@to = params[:to]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Converts an amount into a new currency
|
|
||||||
# @param quote [Quote] the quote object
|
|
||||||
# @return amount [BigDecimal] the converted amount
|
|
||||||
def convert(quote = latest_quote)
|
|
||||||
return amount if quote.base == to
|
|
||||||
amount * quote.rates.fetch(to) { raise Quote::Invalid, 'Invalid to' }
|
|
||||||
end
|
|
||||||
|
|
||||||
def amount
|
|
||||||
BigDecimal(@amount.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def latest_quote
|
|
||||||
@latest_quote ||= Quote.new(base: from)
|
|
||||||
end
|
|
||||||
end
|
|
28
lib/quote.rb
28
lib/quote.rb
@ -5,12 +5,14 @@ require 'currency'
|
|||||||
class Quote
|
class Quote
|
||||||
Invalid = Class.new(StandardError)
|
Invalid = Class.new(StandardError)
|
||||||
|
|
||||||
|
DEFAULT_AMOUNT = 1
|
||||||
DEFAULT_BASE = 'EUR'
|
DEFAULT_BASE = 'EUR'
|
||||||
|
|
||||||
attr_reader :base, :date
|
attr_reader :amount, :base, :date
|
||||||
|
|
||||||
def initialize(params = {})
|
def initialize(params = {})
|
||||||
self.base = params[:base]
|
self.amount = params['amount']
|
||||||
|
self.base = params.values_at(:base, :from).compact.first
|
||||||
self.date = params[:date]
|
self.date = params[:date]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -26,14 +28,18 @@ class Quote
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def base=(base)
|
def amount=(value)
|
||||||
@base = base&.upcase || DEFAULT_BASE
|
@amount = (value || DEFAULT_AMOUNT).to_f
|
||||||
|
raise Invalid, 'Invalid amount' if @amount.zero?
|
||||||
end
|
end
|
||||||
|
|
||||||
def date=(date)
|
def base=(value)
|
||||||
current_date = date ? Currency.current_date_before(date) : Currency.current_date
|
@base = value&.upcase || DEFAULT_BASE
|
||||||
raise Invalid, 'Date too old' unless current_date
|
end
|
||||||
@date = current_date
|
|
||||||
|
def date=(value)
|
||||||
|
@date = value ? Currency.current_date_before(value) : Currency.current_date
|
||||||
|
raise Invalid, 'Date too old' unless @date
|
||||||
rescue Sequel::DatabaseError => ex
|
rescue Sequel::DatabaseError => ex
|
||||||
raise Invalid, 'Invalid date' if ex.wrapped_exception.is_a?(PG::DataException)
|
raise Invalid, 'Invalid date' if ex.wrapped_exception.is_a?(PG::DataException)
|
||||||
raise
|
raise
|
||||||
@ -49,16 +55,16 @@ class Quote
|
|||||||
|
|
||||||
def find_default_rates
|
def find_default_rates
|
||||||
Currency.where(date: date).reduce({}) do |rates, currency|
|
Currency.where(date: date).reduce({}) do |rates, currency|
|
||||||
rates.update(currency.to_h)
|
rates.update(Hash[currency.to_h.map { |k, v| [k, round_rate(v * amount)] }])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_rebased_rates
|
def find_rebased_rates
|
||||||
rates = find_default_rates
|
rates = find_default_rates
|
||||||
denominator = rates.update(DEFAULT_BASE => 1.0).delete(base)
|
denominator = rates.update(DEFAULT_BASE => amount).delete(base)
|
||||||
raise Invalid, 'Invalid base' unless denominator
|
raise Invalid, 'Invalid base' unless denominator
|
||||||
rates.each do |iso_code, rate|
|
rates.each do |iso_code, rate|
|
||||||
rates[iso_code] = round_rate(rate / denominator)
|
rates[iso_code] = round_rate(amount * rate / denominator)
|
||||||
end
|
end
|
||||||
|
|
||||||
rates
|
rates
|
||||||
|
@ -26,11 +26,26 @@ describe 'the API' do
|
|||||||
json['base'].must_equal 'USD'
|
json['base'].must_equal 'USD'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sets base amount' do
|
||||||
|
get '/latest?amount=10'
|
||||||
|
json['rates']['USD'].must_be :>, 10
|
||||||
|
end
|
||||||
|
|
||||||
it 'filters symbols' do
|
it 'filters symbols' do
|
||||||
get '/latest?symbols=USD'
|
get '/latest?symbols=USD'
|
||||||
json['rates'].keys.must_equal %w(USD)
|
json['rates'].keys.must_equal %w(USD)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'aliases base as from' do
|
||||||
|
get '/latest?from=USD'
|
||||||
|
json['base'].must_equal 'USD'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'aliases symbols as to' do
|
||||||
|
get '/latest?to=USD'
|
||||||
|
json['rates'].keys.must_equal %w(USD)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns historical quotes' do
|
it 'returns historical quotes' do
|
||||||
get '/2012-11-20'
|
get '/2012-11-20'
|
||||||
json['rates'].wont_be :empty?
|
json['rates'].wont_be :empty?
|
||||||
@ -67,8 +82,8 @@ describe 'the API' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns converted amount' do
|
it 'converts an amount' do
|
||||||
get '/converter?to=USD&amount=100'
|
get '/latest?from=GBP&to=USD&amount=100'
|
||||||
json['amount'].wont_be :nil?
|
json['rates']['USD'].must_be :>, 100
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require_relative 'helper'
|
|
||||||
require 'converter'
|
|
||||||
|
|
||||||
describe Converter do
|
|
||||||
let(:to) { 'USD' }
|
|
||||||
let(:from) { 'EUR' }
|
|
||||||
let(:amount) { '100.0' }
|
|
||||||
|
|
||||||
let(:converter) { Converter.new to: to, from: from, amount: amount }
|
|
||||||
let(:quote) { Quote.new }
|
|
||||||
|
|
||||||
describe '#amount' do
|
|
||||||
it 'casts to BigDecimal' do
|
|
||||||
converter.amount.must_be_kind_of BigDecimal
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#convert' do
|
|
||||||
it 'should return the amount' do
|
|
||||||
quote.stub :base, to do
|
|
||||||
converter.convert(quote).must_equal BigDecimal(amount)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should use the rates to convert' do
|
|
||||||
quote.stub :rates, Hash(to => 1.2) do
|
|
||||||
converter.convert(quote).must_equal BigDecimal('120.0')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should raise with invalid currency' do
|
|
||||||
quote.stub :rates, {} do
|
|
||||||
-> { converter.convert(quote) }.must_raise Quote::Invalid, 'Invalid to'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should use latest quote as default' do
|
|
||||||
quote.stub :rates, Hash(to => 1.3) do
|
|
||||||
converter.stub :latest_quote, quote do
|
|
||||||
converter.convert.must_equal 130.0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -34,6 +34,12 @@ describe 'the API' do
|
|||||||
json.wont_be_empty
|
json.wont_be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'will not process an invalid amount' do
|
||||||
|
get '/latest?amount=foo'
|
||||||
|
last_response.must_be :unprocessable?
|
||||||
|
json.wont_be_empty
|
||||||
|
end
|
||||||
|
|
||||||
it 'handles malformed queries' do
|
it 'handles malformed queries' do
|
||||||
get '/latest?base=USD?callback=?'
|
get '/latest?base=USD?callback=?'
|
||||||
last_response.must_be :unprocessable?
|
last_response.must_be :unprocessable?
|
||||||
|
Loading…
Reference in New Issue
Block a user