russian_central_bank 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +44 -0
- data/.gitignore +0 -5
- data/.rubocop.yml +8 -0
- data/README.md +34 -10
- data/lib/money/bank/russian_central_bank.rb +85 -0
- data/lib/money/bank/russian_central_bank_fetcher.rb +49 -0
- data/lib/russian_central_bank.rb +2 -83
- data/russian_central_bank.gemspec +7 -4
- data/spec/integration/update_rates_spec.rb +47 -0
- data/spec/lib/money/bank/russian_central_bank_fetcher_spec.rb +77 -0
- data/spec/lib/money/bank/russian_central_bank_spec.rb +112 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/support/rcb_response.xml +53 -0
- metadata +67 -19
- data/spec/russian_central_bank_spec.rb +0 -108
- data/spec/support/daily_rates.yml +0 -8
- data/spec/support/helpers.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e8cf3185a50f7bb0d936780cba98e25c4237ac7
|
4
|
+
data.tar.gz: add5761d3e996d857e02717df3dce9c2cfeb61b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c60395ac140229da981f938d74bc8e55897a36da2aa8034491990a6197f6a74d7e2b7910359f68a218c2fdc9309f4cf66bf980bad08c2796b08836522470e24
|
7
|
+
data.tar.gz: eb487d9d5a23cc45df699e90f49abba331e7e2ed166cff25c0a19a69e262ade0289cf0cbaa9c34664d6252610208827338850149925d33263e3c779c4dc782f2
|
@@ -0,0 +1,44 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.4.2
|
6
|
+
environment:
|
7
|
+
BUNDLE_JOBS: 3
|
8
|
+
BUNDLE_RETRY: 3
|
9
|
+
BUNDLE_PATH: vendor/bundle
|
10
|
+
steps:
|
11
|
+
- checkout
|
12
|
+
|
13
|
+
# Which version of bundler?
|
14
|
+
- run:
|
15
|
+
name: Which bundler?
|
16
|
+
command: bundle -v
|
17
|
+
|
18
|
+
# Restore bundle cache
|
19
|
+
- restore_cache:
|
20
|
+
keys:
|
21
|
+
- russian_central_bank-{{ checksum "russian_central_bank.gemspec" }}
|
22
|
+
- russian_central_bank
|
23
|
+
|
24
|
+
- run:
|
25
|
+
name: Bundle Install
|
26
|
+
command: bundle check || bundle install
|
27
|
+
|
28
|
+
# Store bundle cache
|
29
|
+
- save_cache:
|
30
|
+
key: russian_central_bank-{{ checksum "russian_central_bank.gemspec" }}
|
31
|
+
paths:
|
32
|
+
- vendor/bundle
|
33
|
+
|
34
|
+
# Run rspec
|
35
|
+
- run:
|
36
|
+
name: Run rspec
|
37
|
+
command: |
|
38
|
+
gem install rspec && \
|
39
|
+
bundle exec rspec --format progress \
|
40
|
+
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
41
|
+
|
42
|
+
# Save test results for timing analysis
|
43
|
+
- store_test_results:
|
44
|
+
path: test_results
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/README.md
CHANGED
@@ -19,19 +19,20 @@ Or install it yourself as:
|
|
19
19
|
|
20
20
|
$ gem install russian_central_bank
|
21
21
|
|
22
|
-
NOTE: use 0.x version of `russian_central_bank` for
|
22
|
+
NOTE: use 0.x version of `russian_central_bank` for `money` versions < 6.0
|
23
23
|
|
24
|
-
##Dependencies
|
24
|
+
## Dependencies
|
25
25
|
|
26
|
-
* [
|
26
|
+
* [httparty](https://github.com/jnunemaker/httparty)
|
27
27
|
* [money](https://github.com/RubyMoney/money)
|
28
28
|
|
29
29
|
## Usage
|
30
30
|
|
31
|
-
Regular usage
|
31
|
+
### Regular usage
|
32
32
|
|
33
33
|
require 'russian_central_bank'
|
34
34
|
|
35
|
+
Money.locale_backend = :currency
|
35
36
|
bank = Money::Bank::RussianCentralBank.new
|
36
37
|
|
37
38
|
Money.default_bank = bank
|
@@ -39,14 +40,17 @@ Regular usage
|
|
39
40
|
# Load today's rates
|
40
41
|
bank.update_rates
|
41
42
|
|
42
|
-
# Exchange
|
43
|
-
Money.new(
|
43
|
+
# Exchange 1000 USD to RUB
|
44
|
+
Money.new(1000_00, "USD").exchange_to('RUB').format # => 64.592,50 ₽
|
44
45
|
|
45
|
-
|
46
|
+
# Use indirect exchange rates, USD -> RUB -> EUR
|
47
|
+
Money.new(1000_00, "USD").exchange_to('EUR').format # => €888,26
|
48
|
+
|
49
|
+
### Specific date rates
|
46
50
|
|
47
51
|
# Specify rates date
|
48
|
-
bank.update_rates(Date.
|
49
|
-
Money.new(
|
52
|
+
bank.update_rates(Date.new(2010, 12, 31))
|
53
|
+
Money.new(1000_00, "USD").exchange_to('RUB').format # => 30.476,90 ₽
|
50
54
|
|
51
55
|
# Check last rates update
|
52
56
|
bank.rates_updated_at
|
@@ -54,7 +58,7 @@ Specific date rates
|
|
54
58
|
# Check on which date rates were updated
|
55
59
|
bank.rates_updated_on
|
56
60
|
|
57
|
-
Autoupdate
|
61
|
+
### Autoupdate
|
58
62
|
|
59
63
|
# Use ttl attribute to enable rates autoupdate
|
60
64
|
bank.ttl = 1.day
|
@@ -62,6 +66,26 @@ Autoupdate
|
|
62
66
|
# Check expiration date
|
63
67
|
bank.rates_expired_at
|
64
68
|
|
69
|
+
### Safe rates fetch
|
70
|
+
|
71
|
+
There are some cases, when the `cbr.ru` doesn't return HTTP 200.
|
72
|
+
To avoid issues in production, you use fallback:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
bank = Money::Bank::RussianCentralBank.new
|
76
|
+
begin
|
77
|
+
bank.update_rates
|
78
|
+
rescue Money::Bank::RussianCentralBankFetcher::FetchError => e
|
79
|
+
Rails.logger.info "CBR failed: #{e.response}"
|
80
|
+
|
81
|
+
## fallback
|
82
|
+
Money.default_bank = Money::Bank::VariableExchange.new
|
83
|
+
|
84
|
+
Money.default_bank.add_rate(:usd, :eur, 1.3)
|
85
|
+
Money.default_bank.add_rate(:eur, :usd, 0.7)
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
65
89
|
## Contributing
|
66
90
|
|
67
91
|
1. Fork it
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'money'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
module Bank
|
5
|
+
class RussianCentralBank < Money::Bank::VariableExchange
|
6
|
+
attr_reader :rates_updated_at, :rates_updated_on, :rates_expired_at, :ttl
|
7
|
+
|
8
|
+
def flush_rates
|
9
|
+
@store = Money::RatesStore::Memory.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_rates(date = Date.today)
|
13
|
+
store.transaction do
|
14
|
+
update_parsed_rates(exchange_rates(date))
|
15
|
+
@rates_updated_at = Time.now
|
16
|
+
@rates_updated_on = date
|
17
|
+
update_expired_at
|
18
|
+
store.send(:index)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_rate(from, to, rate)
|
23
|
+
super(from, to, rate)
|
24
|
+
super(to, from, 1.0 / rate)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_rate(from, to)
|
28
|
+
update_rates if rates_expired?
|
29
|
+
super || indirect_rate(from, to)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ttl=(value)
|
33
|
+
@ttl = value
|
34
|
+
update_expired_at
|
35
|
+
@ttl
|
36
|
+
end
|
37
|
+
|
38
|
+
def rates_expired?
|
39
|
+
rates_expired_at && rates_expired_at <= Time.now
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def fetcher
|
45
|
+
@fetcher ||= RussianCentralBankFetcher.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def exchange_rates(date)
|
49
|
+
fetcher.perform(date)
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_expired_at
|
53
|
+
@rates_expired_at = if ttl
|
54
|
+
@rates_updated_at ? @rates_updated_at + ttl : Time.now
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def indirect_rate(from, to)
|
61
|
+
get_rate('RUB', to) / get_rate('RUB', from)
|
62
|
+
end
|
63
|
+
|
64
|
+
def local_currencies
|
65
|
+
@local_currencies ||= Money::Currency.table.map { |currency| currency.last[:iso_code] }
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_parsed_rates(rates)
|
69
|
+
add_rate('RUB', 'RUB', 1)
|
70
|
+
rates.each do |rate|
|
71
|
+
begin
|
72
|
+
if local_currencies.include?(rate[:code])
|
73
|
+
add_rate(
|
74
|
+
'RUB',
|
75
|
+
rate[:code],
|
76
|
+
1 / (rate[:value] / rate[:nominal])
|
77
|
+
)
|
78
|
+
end
|
79
|
+
rescue Money::Currency::UnknownCurrency
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
module Bank
|
5
|
+
class RussianCentralBankFetcher
|
6
|
+
DAILY_RATES_URL = 'http://www.cbr.ru/scripts/XML_daily.asp'.freeze
|
7
|
+
|
8
|
+
class FetchError < StandardError
|
9
|
+
attr_reader :response
|
10
|
+
|
11
|
+
def initialize(message, response = nil)
|
12
|
+
super(message)
|
13
|
+
@response = response
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform(date = Date.today)
|
18
|
+
response = HTTParty.get(rates_url(date))
|
19
|
+
unless response.success?
|
20
|
+
raise_fetch_error("cbr.ru respond with #{response.code}", response)
|
21
|
+
end
|
22
|
+
extract_rates(response.parsed_response)
|
23
|
+
rescue HTTParty::Error => e
|
24
|
+
raise_fetch_error(e.message)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def raise_fetch_error(message, response = nil)
|
30
|
+
raise FetchError.new(message, response)
|
31
|
+
end
|
32
|
+
|
33
|
+
def rates_url(date)
|
34
|
+
"#{DAILY_RATES_URL}?date_req=#{date.strftime('%d/%m/%Y')}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_rates(parsed_response)
|
38
|
+
rates_arr = parsed_response['ValCurs']['Valute']
|
39
|
+
rates_arr.map do |rate|
|
40
|
+
{
|
41
|
+
code: rate['CharCode'],
|
42
|
+
nominal: rate['Nominal'].to_i,
|
43
|
+
value: rate['Value'].tr(',', '.').to_f
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/russian_central_bank.rb
CHANGED
@@ -1,83 +1,2 @@
|
|
1
|
-
require 'money'
|
2
|
-
require '
|
3
|
-
|
4
|
-
class Money
|
5
|
-
module Bank
|
6
|
-
class RussianCentralBank < Money::Bank::VariableExchange
|
7
|
-
|
8
|
-
CBR_SERVICE_URL = 'http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL'
|
9
|
-
|
10
|
-
attr_reader :rates_updated_at, :rates_updated_on, :ttl, :rates_expired_at
|
11
|
-
|
12
|
-
def flush_rates
|
13
|
-
@store = Money::RatesStore::Memory.new
|
14
|
-
end
|
15
|
-
|
16
|
-
def update_rates(date = Date.today)
|
17
|
-
store.transaction do
|
18
|
-
update_parsed_rates(exchange_rates(date))
|
19
|
-
@rates_updated_at = Time.now
|
20
|
-
@rates_updated_on = date
|
21
|
-
update_expired_at
|
22
|
-
store.send(:index)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def add_rate(from, to, rate)
|
27
|
-
super(from, to, rate)
|
28
|
-
super(to, from, 1.0 / rate)
|
29
|
-
end
|
30
|
-
|
31
|
-
def get_rate(from, to)
|
32
|
-
update_rates if rates_expired?
|
33
|
-
super || indirect_rate(from, to)
|
34
|
-
end
|
35
|
-
|
36
|
-
def ttl=(value)
|
37
|
-
@ttl = value
|
38
|
-
update_expired_at
|
39
|
-
@ttl
|
40
|
-
end
|
41
|
-
|
42
|
-
def rates_expired?
|
43
|
-
rates_expired_at && rates_expired_at <= Time.now
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def update_expired_at
|
49
|
-
@rates_expired_at = if ttl
|
50
|
-
@rates_updated_at ? @rates_updated_at + ttl : Time.now
|
51
|
-
else
|
52
|
-
nil
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def indirect_rate(from, to)
|
57
|
-
from_base_rate = get_rate('RUB', from)
|
58
|
-
to_base_rate = get_rate('RUB', to)
|
59
|
-
to_base_rate / from_base_rate
|
60
|
-
end
|
61
|
-
|
62
|
-
def exchange_rates(date = Date.today)
|
63
|
-
client = Savon::Client.new wsdl: CBR_SERVICE_URL, log: false, log_level: :error,
|
64
|
-
follow_redirects: true
|
65
|
-
response = client.call(:get_curs_on_date, message: { 'On_date' => date.strftime('%Y-%m-%dT%H:%M:%S') })
|
66
|
-
response.body[:get_curs_on_date_response][:get_curs_on_date_result][:diffgram][:valute_data][:valute_curs_on_date]
|
67
|
-
end
|
68
|
-
|
69
|
-
def update_parsed_rates(rates)
|
70
|
-
local_currencies = Money::Currency.table.map { |currency| currency.last[:iso_code] }
|
71
|
-
add_rate('RUB', 'RUB', 1)
|
72
|
-
rates.each do |rate|
|
73
|
-
begin
|
74
|
-
if local_currencies.include? rate[:vch_code]
|
75
|
-
add_rate('RUB', rate[:vch_code], 1/ (rate[:vcurs].to_f / rate[:vnom].to_i))
|
76
|
-
end
|
77
|
-
rescue Money::Currency::UnknownCurrency
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
1
|
+
require 'money/bank/russian_central_bank_fetcher'
|
2
|
+
require 'money/bank/russian_central_bank'
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'russian_central_bank'
|
7
|
-
spec.version = '1.0
|
7
|
+
spec.version = '1.1.0'
|
8
8
|
spec.authors = ['Ramil Mustafin']
|
9
9
|
spec.email = ['rommel.rmm@gmail.com']
|
10
10
|
spec.description = 'RussianCentralBank extends Money::Bank::VariableExchange and gives you access to the Central Bank of Russia currency exchange rates.'
|
@@ -17,10 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ['lib']
|
19
19
|
|
20
|
-
spec.add_development_dependency 'bundler'
|
20
|
+
spec.add_development_dependency 'bundler'
|
21
|
+
spec.add_development_dependency 'pry'
|
21
22
|
spec.add_development_dependency 'rake'
|
22
23
|
spec.add_development_dependency 'rspec', '~>3'
|
24
|
+
spec.add_development_dependency 'rubocop'
|
25
|
+
spec.add_development_dependency 'webmock', '~>3'
|
23
26
|
|
24
|
-
spec.add_dependency '
|
25
|
-
spec.add_dependency '
|
27
|
+
spec.add_dependency 'httparty', '~>0'
|
28
|
+
spec.add_dependency 'money', '~> 6'
|
26
29
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Update Rates' do
|
4
|
+
let(:bank) { Money::Bank::RussianCentralBank.new }
|
5
|
+
let(:rates_xml) { File.read('spec/support/rcb_response.xml') }
|
6
|
+
let(:date) { Date.today }
|
7
|
+
let(:rates_url) do
|
8
|
+
"#{Money::Bank::RussianCentralBankFetcher::DAILY_RATES_URL}?date_req=#{date.strftime('%d/%m/%Y')}"
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when cbr.ru successfully returns rates' do
|
12
|
+
before do
|
13
|
+
stub_request(:get, rates_url).to_return(
|
14
|
+
status: 200,
|
15
|
+
body: rates_xml,
|
16
|
+
headers: {
|
17
|
+
"content-type"=>["application/xml; charset=windows-1251"]
|
18
|
+
}
|
19
|
+
)
|
20
|
+
|
21
|
+
bank.update_rates
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'adds rates', :aggregete_failure do
|
25
|
+
expect(bank.get_rate('USD', 'RUB')).to eq(64.4993)
|
26
|
+
expect(bank.get_rate('RUB', 'USD')).to eq(0.015504044229937378)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'adds rates for currencies with nominal > 1', :aggregete_failure do
|
30
|
+
expect(bank.get_rate('DKK', 'RUB')).to eq(9.76581)
|
31
|
+
expect(bank.get_rate('RUB', 'DKK')).to eq(0.10239806017114812)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when cbr.ru request fails' do
|
36
|
+
before do
|
37
|
+
stub_request(:get, rates_url).to_return(
|
38
|
+
status: 503,
|
39
|
+
body: '503'
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises FetchError' do
|
44
|
+
expect { bank.update_rates }.to raise_error(Money::Bank::RussianCentralBankFetcher::FetchError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Money::Bank::RussianCentralBankFetcher do
|
4
|
+
describe 'perform' do
|
5
|
+
subject(:perform) { fetcher.perform(date) }
|
6
|
+
|
7
|
+
let(:date) { Date.today }
|
8
|
+
let(:fetcher) { described_class.new }
|
9
|
+
let(:rates_url) do
|
10
|
+
"http://www.cbr.ru/scripts/XML_daily.asp?date_req=#{date.strftime('%d/%m/%Y')}"
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when RCB responds' do
|
14
|
+
let(:rcb_response) do
|
15
|
+
instance_double(
|
16
|
+
'response',
|
17
|
+
parsed_response: parsed_response,
|
18
|
+
success?: response_is_success,
|
19
|
+
code: response_code
|
20
|
+
)
|
21
|
+
end
|
22
|
+
let(:parsed_response) do
|
23
|
+
{
|
24
|
+
'ValCurs' => {
|
25
|
+
'Valute' => [
|
26
|
+
{
|
27
|
+
'CharCode' => 'XXX',
|
28
|
+
'Nominal' => '1',
|
29
|
+
'Value' => '100,1'
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
before do
|
37
|
+
allow(HTTParty).to receive(:get).with(rates_url).and_return(rcb_response)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'and respond is successfull' do
|
41
|
+
let(:response_is_success) { true }
|
42
|
+
let(:response_code) { 200 }
|
43
|
+
|
44
|
+
it 'returns a normalized array of rates' do
|
45
|
+
expect(perform).to eq(
|
46
|
+
[
|
47
|
+
{
|
48
|
+
code: 'XXX', nominal: 1, value: 100.1
|
49
|
+
}
|
50
|
+
]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'and repsond is not successfull' do
|
56
|
+
let(:response_is_success) { false }
|
57
|
+
let(:response_code) { 503 }
|
58
|
+
|
59
|
+
it 'raises FetchError' do
|
60
|
+
expect { perform }.to raise_error(Money::Bank::RussianCentralBankFetcher::FetchError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when RCB fails to respond' do
|
66
|
+
let(:error_message) { 'RCB failed to response' }
|
67
|
+
|
68
|
+
before do
|
69
|
+
allow(HTTParty).to receive(:get).with(rates_url).and_raise(HTTParty::Error, error_message)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'rescues an exception and raises FetchError' do
|
73
|
+
expect { perform }.to raise_error(Money::Bank::RussianCentralBankFetcher::FetchError, error_message)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Money::Bank::RussianCentralBank do
|
4
|
+
subject(:bank) { described_class.new }
|
5
|
+
|
6
|
+
describe '#update_rates' do
|
7
|
+
let(:fetcher) { instance_double(Money::Bank::RussianCentralBankFetcher, perform: rates) }
|
8
|
+
let(:rates) do
|
9
|
+
[
|
10
|
+
{ code: 'USD', value: 32.4288, nominal: 1 },
|
11
|
+
{ code: 'EUR', value: 42.5920, nominal: 1 },
|
12
|
+
{ code: 'JPY', value: 32.4029, nominal: 100 }
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(Money::Bank::RussianCentralBankFetcher).to receive(:new).and_return(fetcher)
|
18
|
+
|
19
|
+
bank.update_rates
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should update rates from daily rates service' do
|
23
|
+
expect(bank.rates['RUB_TO_USD']).to eq(0.03083678705348332)
|
24
|
+
expect(bank.rates['RUB_TO_EUR']).to eq(0.023478587528174305)
|
25
|
+
expect(bank.rates['RUB_TO_JPY']).to eq(3.086143524190736)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#flush_rates' do
|
30
|
+
before do
|
31
|
+
bank.add_rate('RUB', 'USD', 0.03)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should delete all rates' do
|
35
|
+
bank.get_rate('RUB', 'USD')
|
36
|
+
bank.flush_rates
|
37
|
+
expect(bank.store.send(:index)).to be_empty
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#get_rate' do
|
42
|
+
context 'when getting direct rates' do
|
43
|
+
before do
|
44
|
+
bank.flush_rates
|
45
|
+
bank.add_rate('RUB', 'USD', 0.03)
|
46
|
+
bank.add_rate('RUB', 'GBP', 0.02)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should get rate from @rates' do
|
50
|
+
expect(bank.get_rate('RUB', 'USD')).to eq(0.03)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should calculate indirect rates' do
|
54
|
+
expect(bank.get_rate('USD', 'GBP')).to eq(0.6666666666666667)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when getting indirect rate' do
|
59
|
+
let(:indirect_rate) { 4 }
|
60
|
+
|
61
|
+
before do
|
62
|
+
bank.flush_rates
|
63
|
+
bank.add_rate('RUB', 'USD', 123)
|
64
|
+
bank.add_rate('USD', 'RUB', indirect_rate)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'gets indirect rate from the last set' do
|
68
|
+
expect(bank.get_rate('RUB', 'USD')).to eq(1.0 / indirect_rate)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when ttl is not set' do
|
73
|
+
before do
|
74
|
+
bank.add_rate('RUB', 'USD', 123)
|
75
|
+
bank.ttl = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should not update rates' do
|
79
|
+
expect(bank).to_not receive(:update_rates)
|
80
|
+
bank.get_rate('RUB', 'USD')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when ttl is set' do
|
85
|
+
before { bank.add_rate('RUB', 'USD', 123) }
|
86
|
+
|
87
|
+
context 'and raks are expired' do
|
88
|
+
before do
|
89
|
+
bank.instance_variable_set('@rates_updated_at', Time.now - 3600)
|
90
|
+
bank.ttl = 3600
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should update rates' do
|
94
|
+
expect(bank).to receive(:update_rates)
|
95
|
+
bank.get_rate('RUB', 'USD')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'and ranks are not expired' do
|
100
|
+
before do
|
101
|
+
bank.instance_variable_set('@rates_updated_at', Time.now - 3000)
|
102
|
+
bank.ttl = 3600
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should not update rates' do
|
106
|
+
expect(bank).to_not receive(:update_rates)
|
107
|
+
bank.get_rate('RUB', 'USD')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="windows-1251"?>
|
3
|
+
<ValCurs Date="26.03.2019" name="Foreign Currency Market">
|
4
|
+
<Valute ID="R01010">
|
5
|
+
<NumCode>036</NumCode>
|
6
|
+
<CharCode>AUD</CharCode>
|
7
|
+
<Nominal>1</Nominal>
|
8
|
+
<Name>Австралийский доллар</Name>
|
9
|
+
<Value>45,7365</Value>
|
10
|
+
</Valute>
|
11
|
+
<Valute ID="R01035">
|
12
|
+
<NumCode>826</NumCode>
|
13
|
+
<CharCode>GBP</CharCode>
|
14
|
+
<Nominal>1</Nominal>
|
15
|
+
<Name>Фунт стерлингов Соединенного королевства</Name>
|
16
|
+
<Value>85,0552</Value>
|
17
|
+
</Valute>
|
18
|
+
<Valute ID="R01200">
|
19
|
+
<NumCode>344</NumCode>
|
20
|
+
<CharCode>HKD</CharCode>
|
21
|
+
<Nominal>10</Nominal>
|
22
|
+
<Name>Гонконгских долларов</Name>
|
23
|
+
<Value>82,1815</Value>
|
24
|
+
</Valute>
|
25
|
+
<Valute ID="R01215">
|
26
|
+
<NumCode>208</NumCode>
|
27
|
+
<CharCode>DKK</CharCode>
|
28
|
+
<Nominal>10</Nominal>
|
29
|
+
<Name>Датских крон</Name>
|
30
|
+
<Value>97,6581</Value>
|
31
|
+
</Valute>
|
32
|
+
<Valute ID="R01235">
|
33
|
+
<NumCode>840</NumCode>
|
34
|
+
<CharCode>USD</CharCode>
|
35
|
+
<Nominal>1</Nominal>
|
36
|
+
<Name>Доллар США</Name>
|
37
|
+
<Value>64,4993</Value>
|
38
|
+
</Valute>
|
39
|
+
<Valute ID="R01239">
|
40
|
+
<NumCode>978</NumCode>
|
41
|
+
<CharCode>EUR</CharCode>
|
42
|
+
<Nominal>1</Nominal>
|
43
|
+
<Name>Евро</Name>
|
44
|
+
<Value>72,9229</Value>
|
45
|
+
</Valute>
|
46
|
+
<Valute ID="R01820">
|
47
|
+
<NumCode>392</NumCode>
|
48
|
+
<CharCode>JPY</CharCode>
|
49
|
+
<Nominal>100</Nominal>
|
50
|
+
<Name>Японских иен</Name>
|
51
|
+
<Value>58,5958</Value>
|
52
|
+
</Valute>
|
53
|
+
</ValCurs>
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: russian_central_bank
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ramil Mustafin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,33 +67,61 @@ dependencies:
|
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '3'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: httparty
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
58
100
|
requirements:
|
59
101
|
- - "~>"
|
60
102
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
103
|
+
version: '0'
|
62
104
|
type: :runtime
|
63
105
|
prerelease: false
|
64
106
|
version_requirements: !ruby/object:Gem::Requirement
|
65
107
|
requirements:
|
66
108
|
- - "~>"
|
67
109
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
110
|
+
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
112
|
+
name: money
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
72
114
|
requirements:
|
73
115
|
- - "~>"
|
74
116
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
117
|
+
version: '6'
|
76
118
|
type: :runtime
|
77
119
|
prerelease: false
|
78
120
|
version_requirements: !ruby/object:Gem::Requirement
|
79
121
|
requirements:
|
80
122
|
- - "~>"
|
81
123
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
124
|
+
version: '6'
|
83
125
|
description: RussianCentralBank extends Money::Bank::VariableExchange and gives you
|
84
126
|
access to the Central Bank of Russia currency exchange rates.
|
85
127
|
email:
|
@@ -88,17 +130,22 @@ executables: []
|
|
88
130
|
extensions: []
|
89
131
|
extra_rdoc_files: []
|
90
132
|
files:
|
133
|
+
- ".circleci/config.yml"
|
91
134
|
- ".gitignore"
|
135
|
+
- ".rubocop.yml"
|
92
136
|
- Gemfile
|
93
137
|
- LICENSE.txt
|
94
138
|
- README.md
|
95
139
|
- Rakefile
|
140
|
+
- lib/money/bank/russian_central_bank.rb
|
141
|
+
- lib/money/bank/russian_central_bank_fetcher.rb
|
96
142
|
- lib/russian_central_bank.rb
|
97
143
|
- russian_central_bank.gemspec
|
98
|
-
- spec/
|
144
|
+
- spec/integration/update_rates_spec.rb
|
145
|
+
- spec/lib/money/bank/russian_central_bank_fetcher_spec.rb
|
146
|
+
- spec/lib/money/bank/russian_central_bank_spec.rb
|
99
147
|
- spec/spec_helper.rb
|
100
|
-
- spec/support/
|
101
|
-
- spec/support/helpers.rb
|
148
|
+
- spec/support/rcb_response.xml
|
102
149
|
homepage: http://github.com/rmustafin/russian_central_bank
|
103
150
|
licenses:
|
104
151
|
- MIT
|
@@ -119,12 +166,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
166
|
version: '0'
|
120
167
|
requirements: []
|
121
168
|
rubyforge_project:
|
122
|
-
rubygems_version: 2.
|
169
|
+
rubygems_version: 2.6.11
|
123
170
|
signing_key:
|
124
171
|
specification_version: 4
|
125
172
|
summary: Access to Central Bank of Russia currency exchange rates.
|
126
173
|
test_files:
|
127
|
-
- spec/
|
174
|
+
- spec/integration/update_rates_spec.rb
|
175
|
+
- spec/lib/money/bank/russian_central_bank_fetcher_spec.rb
|
176
|
+
- spec/lib/money/bank/russian_central_bank_spec.rb
|
128
177
|
- spec/spec_helper.rb
|
129
|
-
- spec/support/
|
130
|
-
- spec/support/helpers.rb
|
178
|
+
- spec/support/rcb_response.xml
|
@@ -1,108 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
describe 'RussianCentralBank' do
|
4
|
-
before do
|
5
|
-
rates_hash = symbolize_keys YAML::load(File.open('spec/support/daily_rates.yml'))
|
6
|
-
allow_any_instance_of(Savon::Client).to receive_message_chain(:call, :body) { rates_hash }
|
7
|
-
end
|
8
|
-
|
9
|
-
before :each do
|
10
|
-
@bank = Money::Bank::RussianCentralBank.new
|
11
|
-
end
|
12
|
-
|
13
|
-
describe '#update_rates' do
|
14
|
-
before do
|
15
|
-
@bank.update_rates
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'should update rates from daily rates service' do
|
19
|
-
expect(@bank.rates['RUB_TO_USD']).to eq(0.03083678705348332)
|
20
|
-
expect(@bank.rates['RUB_TO_EUR']).to eq(0.023478587528174305)
|
21
|
-
expect(@bank.rates['RUB_TO_JPY']).to eq(3.086143524190736)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
describe '#flush_rates' do
|
26
|
-
before do
|
27
|
-
@bank.add_rate('RUB', 'USD', 0.03)
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should delete all rates' do
|
31
|
-
@bank.get_rate('RUB', 'USD')
|
32
|
-
@bank.flush_rates
|
33
|
-
expect(@bank.store.send(:index)).to be_empty
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe '#get_rate' do
|
38
|
-
context 'getting dicrect rates' do
|
39
|
-
before do
|
40
|
-
@bank.flush_rates
|
41
|
-
@bank.add_rate('RUB', 'USD', 0.03)
|
42
|
-
@bank.add_rate('RUB', 'GBP', 0.02)
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'should get rate from @rates' do
|
46
|
-
expect(@bank.get_rate('RUB', 'USD')).to eq(0.03)
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'should calculate indirect rates' do
|
50
|
-
expect(@bank.get_rate('USD', 'GBP')).to eq(0.6666666666666667)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'getting indirect rate' do
|
55
|
-
let(:indirect_rate) { 4 }
|
56
|
-
|
57
|
-
before do
|
58
|
-
@bank.flush_rates
|
59
|
-
@bank.add_rate('RUB', 'USD', 123)
|
60
|
-
@bank.add_rate('USD', 'RUB', indirect_rate)
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'gets indirect rate from the last set' do
|
64
|
-
expect(@bank.get_rate('RUB', 'USD')).to eq(1.0 / indirect_rate)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
context "when ttl is not set" do
|
69
|
-
before do
|
70
|
-
@bank.add_rate('RUB', 'USD', 123)
|
71
|
-
@bank.ttl = nil
|
72
|
-
end
|
73
|
-
|
74
|
-
it "should not update rates" do
|
75
|
-
expect(@bank).to_not receive(:update_rates)
|
76
|
-
@bank.get_rate('RUB', 'USD')
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context "when ttl is set" do
|
81
|
-
before { @bank.add_rate('RUB', 'USD', 123) }
|
82
|
-
|
83
|
-
context "and raks are expired" do
|
84
|
-
before do
|
85
|
-
@bank.instance_variable_set('@rates_updated_at', Time.now - 3600)
|
86
|
-
@bank.ttl = 3600
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should update rates" do
|
90
|
-
expect(@bank).to receive(:update_rates)
|
91
|
-
@bank.get_rate('RUB', 'USD')
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context "and ranks are not expired" do
|
96
|
-
before do
|
97
|
-
@bank.instance_variable_set('@rates_updated_at', Time.now - 3000)
|
98
|
-
@bank.ttl = 3600
|
99
|
-
end
|
100
|
-
|
101
|
-
it "should not update rates" do
|
102
|
-
expect(@bank).to_not receive(:update_rates)
|
103
|
-
@bank.get_rate('RUB', 'USD')
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
@@ -1,8 +0,0 @@
|
|
1
|
-
get_curs_on_date_response:
|
2
|
-
get_curs_on_date_result:
|
3
|
-
diffgram:
|
4
|
-
valute_data:
|
5
|
-
valute_curs_on_date:
|
6
|
-
- {vname: "US Dollar", vnom: "1", vcurs: "32.4288", vcode: "840", vch_code: "USD"}
|
7
|
-
- {vname: "Euro", vnom: "1", vcurs: "42.5920", vcode: "978", vch_code: "EUR"}
|
8
|
-
- {vname: "Japanese yen", vnom: "100", vcurs: "32.4029", vcode: "392", vch_code: "JPY"}
|
data/spec/support/helpers.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
def symbolize_keys(hash)
|
2
|
-
hash.inject({}){|result, (key, value)|
|
3
|
-
new_key = case key
|
4
|
-
when String then key.to_sym
|
5
|
-
else key
|
6
|
-
end
|
7
|
-
new_value = case value
|
8
|
-
when Hash then symbolize_keys(value)
|
9
|
-
when Array then value.map{ |v| v.is_a?(Hash) ? symbolize_keys(v) : v }
|
10
|
-
else value
|
11
|
-
end
|
12
|
-
result[new_key] = new_value
|
13
|
-
result
|
14
|
-
}
|
15
|
-
end
|