Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: buru/currencylayer
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: Streetbees/currencylayer
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 4 commits
  • 4 files changed
  • 1 contributor

Commits on Nov 28, 2018

  1. Applied monkey patch

    coding-red-panda committed Nov 28, 2018
    Copy the full SHA
    4f9cda6 View commit details

Commits on Nov 29, 2018

  1. Copy the full SHA
    4b5e342 View commit details
  2. Copy the full SHA
    f8cd9cb View commit details
  3. Merge pull request #1 from Streetbees/olivar/monkeypatch

    Applied monkey patch
    coding-red-panda authored Nov 29, 2018
    Copy the full SHA
    2c8f83e View commit details
Showing with 92 additions and 63 deletions.
  1. +2 −2 currencylayer.gemspec
  2. +34 −29 lib/money/bank/currencylayer.rb
  3. +17 −0 lib/money/bank/on_time_patch.rb
  4. +39 −32 spec/currencylayer_spec.rb
4 changes: 2 additions & 2 deletions currencylayer.gemspec
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |spec|
spec.name = "currencylayer"
spec.version = '0.0.1'
spec.version = '0.0.2'
spec.authors = ["Andrey Skuratovsky"]
spec.email = ["skuratowsky@gmail.com"]
spec.summary = "Access to the currencylayer.com online exchange rates"
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
spec.files = Dir.glob("{lib,spec}/**/*")
spec.require_paths = ["lib"]

spec.add_dependency "money", "~> 6"
spec.add_dependency "money", "~> 6.13.1"

spec.add_development_dependency "rspec", ">= 3.0.0"
spec.add_development_dependency "timecop"
63 changes: 34 additions & 29 deletions lib/money/bank/currencylayer.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
require 'money'
require 'open-uri'
require_relative 'on_time_patch'

# Money class, see http://github.com/RubyMoney/money
class Money

# Provides classes that aid in the ability of exchange one currency with
# another.
module Bank

# Exception that will be thrown if jsonrates.com api returns error on api request.
class RequestError < StandardError ; end

@@ -21,16 +21,14 @@ def message

# Money::Bank implementation that gives access to the current exchange rates using jsonrates.com api.
class Currencylayer < Money::Bank::VariableExchange
include OnTimePatch

# Host of service jsonrates
SERVICE_HOST = "apilayer.net"

# Relative path of jsonrates api
SERVICE_PATH = "/api/live"

# @return [Hash] Stores the currently known rates.
attr_reader :rates

# accessor of access_key of jsonrates.com service
attr_accessor :access_key

@@ -80,9 +78,7 @@ def refresh_rates_expiration!
# bank.get_rate(:USD, :EUR) #=> 0.776337241
# bank.flush_rates #=> {}
def flush_rates
@mutex.synchronize{
@rates = {}
}
@store = self.store.class.new
end

##
@@ -100,10 +96,24 @@ def flush_rates
# bank.get_rate(:USD, :EUR) #=> 0.776337241
# bank.flush_rate(:USD, :EUR) #=> 0.776337241
def flush_rate(from, to)
key = rate_key_for(from, to)
@mutex.synchronize{
@rates.delete(key)
rates = {}

self.store.transaction {
self.store.each_rate do |iso_from, iso_to, rate|
next if iso_from == from && iso_to == to
rates[rate_key_for(iso_from, iso_to)] = rate
end
}

@store = self.store.class.new

self.store.transaction do
rates.each do |k, r|
key_from = k.split(self.store.class::INDEX_KEY_SEPARATOR).first
key_to = k.split(self.store.class::INDEX_KEY_SEPARATOR).last
self.store.add_rate(key_from, key_to, r)
end
end
end

##
@@ -139,8 +149,8 @@ def get_rate(from, to)
# bank = Money::Bank::Currencylayer.new #=> <Money::Bank::Currencylayer...>
# bank.add_rate("USD", "CAD", 1.24515) #=> 1.24515
# bank.add_rate("CAD", "USD", 0.803115) #=> 0.803115
def add_rate from, to, rate
set_rate from, to, rate
def add_rate(from, to, rate)
set_rate(from, to, rate)
end

# Set the rate for the given currencies. Uses +Mutex+ to synchronize data
@@ -158,7 +168,7 @@ def add_rate from, to, rate
# @bank = Money::Bank::Currencylayer.new #=> <Money::Bank::Currencylayer...>
# bank.set_rate("USD", "CAD", 1.24515) #=> 1.24515
# bank.set_rate("CAD", "USD", 0.803115) #=> 0.803115
def set_rate from, to, rate
def set_rate(from, to, rate)
if self.class.rates_careful
set_rate_with_time(from, to, rate)
else
@@ -176,14 +186,8 @@ def set_rate from, to, rate
#
# @example
# rate_key_for("USD", "CAD") #=> "USD_TO_CAD"
# Money::Bank::Currencylayer.rates_careful = true
# rate_key_for("USD", "CAD") #=> "USD_TO_CAD_C"
def rate_key_for(from, to)
if self.class.rates_careful
"#{Currency.wrap(from).iso_code}_TO_#{Currency.wrap(to).iso_code}_C".upcase
else
super
end
self.store.send(:rate_key_for, from, to)
end

##
@@ -222,13 +226,12 @@ def expired?
#
# @return [Float] The requested rate.
def get_rate_careful(from, to)

rate_key = rate_key_for(from, to)
rate_cached = @rates[rate_key]
rate_key = rate_key_for(from, to)
rate_cached = self.rates[rate_key]

if rate_cached.nil? || expired_time?(rate_cached[:created_at])
set_rate_with_time(from, to, fetch_rate(from, to))
@rates[rate_key][:rate]
self.rates[rate_key][:rate]
else
rate_cached[:rate]
end
@@ -252,8 +255,8 @@ def get_rate_careful(from, to)
def get_rate_straight(from, to)
expire_rates

@mutex.synchronize{
@rates[rate_key_for(from, to)] ||= fetch_rate(from, to)
self.store.transaction{
self.rates[rate_key_for(from, to)] ||= fetch_rate(from, to)
}
end

@@ -281,9 +284,11 @@ def add_rate_with_time(from, to, rate)
# @return [Numeric]
def set_rate_with_time(from, to, rate)
rate_d = BigDecimal.new(rate.to_s)
@mutex.synchronize {
@rates[rate_key_for(from, to)] = {rate: rate_d, created_at: Time.now}

self.store.transaction {
self.store.add_rate(from, to, { rate: rate_d, created_at: Time.now })
}

rate_d
end

@@ -293,7 +298,7 @@ def set_rate_with_time(from, to, rate)
# @param [Time] time Time to check
#
# @return [Boolean] Is the time expired.
def expired_time? time
def expired_time?(time)
time + self.class.ttl_in_seconds.to_i < Time.now
end

17 changes: 17 additions & 0 deletions lib/money/bank/on_time_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module OnTimePatch
def find_rate_for_date(from, to, date)
date = Date.today unless date.respond_to?(:strftime)

formated_date = date.strftime('%Y-%m-%d')

# The only difference is here, as we should also send
# the specific date query param
uri = build_uri(from, to)
uri.query = "#{uri.query}&date=#{formated_date}"

data = perform_request(uri)
rate = extract_rate(data, from, to)

add_rate(from, to, rate)
end
end
71 changes: 39 additions & 32 deletions spec/currencylayer_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
require 'spec_helper'

describe "Currencylayer" do
let(:bank) { Money::Bank::Currencylayer.new }

before :each do
@bank = Money::Bank::Currencylayer.new
@bank.access_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
bank.access_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

Money::Bank::Currencylayer.rates_careful = false
Money::Bank::Currencylayer.ttl_in_seconds = 86400
end

it "should accept a ttl_in_seconds option" do
@@ -13,40 +17,42 @@

describe '.get_rate' do
it 'returns rate' do
uri = @bank.send(:build_uri, 'USD', 'EUR').to_s
uri = bank.send(:build_uri, 'USD', 'EUR').to_s
stub_request(:get, uri).to_return( :status => 200,
:body => '{"success":true,"terms":"https:\/\/currencylayer.com\/terms","privacy":"https:\/\/currencylayer.com\/privacy","timestamp":1434443053,"source":"USD","quotes":{"USDEUR":0.887701}}')

@bank.flush_rates
rate = @bank.get_rate('USD', 'EUR')
bank.flush_rates

rate = bank.get_rate('USD', 'EUR')

expect(rate).to eq(BigDecimal.new("0.887701"))
end
context "in careful mode" do

it "don't flush rate if get some exception on request" do

context "in careful mode" do
before do
Money::Bank::Currencylayer.rates_careful = true
Money::Bank::Currencylayer.ttl_in_seconds = 0
end

@bank.flush_rates
@bank.add_rate('USD', 'EUR', 1.011)

it "don't flush rate if get some exception on request" do
bank.flush_rates
bank.add_rate('USD', 'EUR', 1.011)

uri = @bank.send(:build_uri, 'USD', 'EUR').to_s
uri = bank.send(:build_uri, 'USD', 'EUR').to_s

stub_request(:get, uri).to_return(:status => 200, :body => '{"success":false,"error":{"code":202,"info":"You have provided one or more invalid Currency Codes. [Required format: currencies=EUR,USD,GBP,...]"}}')
rate = @bank.get_rate('USD', 'EUR')

rate = bank.get_rate('USD', 'EUR')

expect(rate).to eq(BigDecimal.new("1.011"))

Money::Bank::Currencylayer.rates_careful = false
end

end
end

describe ".refresh_rates_expiration!" do
it "set the .rates_expiration using the TTL and the current time" do
Money::Bank::Currencylayer.ttl_in_seconds = 86400
new_time = Time.now
Timecop.freeze(new_time)
Money::Bank::Currencylayer.refresh_rates_expiration!
@@ -56,28 +62,29 @@

describe ".flush_rates" do
it "should empty @rates" do
@bank.add_rate("USD", "CAD", 1.24515)
@bank.flush_rates
expect(@bank.rates).to eq({})
bank.add_rate("USD", "CAD", 1.24515)
bank.flush_rates
expect(bank.rates).to eq({})
end
end

describe 'careful mode' do
it 'returns cached value if exception raised' do
@bank.flush_rates
@bank.add_rate("USD", "CAD", 32.231)
expect(@bank.get_rate("USD", "CAD")).to eq (BigDecimal.new('32.231'))
bank.flush_rates
bank.add_rate("USD", "CAD", 32.231)
expect(bank.get_rate("USD", "CAD")).to eq (BigDecimal.new('32.231'))
end
end

describe ".flush_rate" do
it "should remove a specific rate from @rates" do
@bank.flush_rates
@bank.add_rate('USD', 'EUR', 1.4)
@bank.add_rate('USD', 'JPY', 0.3)
@bank.flush_rate('USD', 'EUR')
expect(@bank.rates).to include('USD_TO_JPY')
expect(@bank.rates).to_not include('USD_TO_EUR')
bank.flush_rates
bank.add_rate('USD', 'EUR', 1.4)
bank.add_rate('USD', 'JPY', 0.3)
bank.flush_rate('USD', 'EUR')

expect(bank.rates).to include('USD_TO_JPY')
expect(bank.rates).to_not include('USD_TO_EUR')
end
end

@@ -93,22 +100,22 @@
end

it "should flush all rates" do
expect(@bank).to receive(:flush_rates)
@bank.expire_rates
expect(bank).to receive(:flush_rates)
bank.expire_rates
end

it "updates the next expiration time" do
exp_time = Time.now + 1000

@bank.expire_rates
bank.expire_rates
expect(Money::Bank::Currencylayer.rates_expiration).to eq(exp_time)
end
end

context "when the ttl has not expired" do
it "not should flush all rates" do
expect(@bank).to_not receive(:flush_rates)
@bank.expire_rates
expect(bank).to_not receive(:flush_rates)
bank.expire_rates
end
end
end