money-ecb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +13 -0
- data/README.rst +85 -0
- data/Rakefile +9 -0
- data/lib/money/bank/ecb.rb +84 -0
- data/money-ecb.gemspec +13 -0
- data/spec/assets/eurofxref.zip +0 -0
- data/spec/assets/good_rates.csv +2 -0
- data/spec/ecb_spec.rb +112 -0
- metadata +88 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.rst
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
money-ecb
|
2
|
+
==========
|
3
|
+
|
4
|
+
.. image:: https://travis-ci.org/ct-clearhaus/money-ecb.png?branch=master
|
5
|
+
:target: https://travis-ci.org/ct-clearhaus/money-ecb
|
6
|
+
|
7
|
+
|
8
|
+
Introduction
|
9
|
+
------------
|
10
|
+
|
11
|
+
This gem is a RubyMoney bank that can exchange ``Money`` using rates from the
|
12
|
+
ECB (European Central Bank).
|
13
|
+
|
14
|
+
|
15
|
+
Installation
|
16
|
+
------------
|
17
|
+
|
18
|
+
.. code-block:: sh
|
19
|
+
|
20
|
+
gem install money-ecb
|
21
|
+
|
22
|
+
Dependencies
|
23
|
+
............
|
24
|
+
|
25
|
+
- RubyMoney's ``money`` gem
|
26
|
+
- ``rubyzip`` gem
|
27
|
+
|
28
|
+
|
29
|
+
Example
|
30
|
+
-------
|
31
|
+
|
32
|
+
Using ``money`` and ``monetize``:
|
33
|
+
|
34
|
+
.. code-block:: ruby
|
35
|
+
|
36
|
+
require 'money'
|
37
|
+
require 'money/bank/ecb'
|
38
|
+
require 'monetize/core_extensions'
|
39
|
+
|
40
|
+
Money.default_bank = Money::Bank::ECB.new('/tmp/ecb-fxr.cache')
|
41
|
+
|
42
|
+
puts '1 EUR'.to_money.exchange_to(:USD)
|
43
|
+
|
44
|
+
If ``/tmp/ecb-fxr.cache`` is a valid CSV file with exchange rates, the rates
|
45
|
+
will be used for conversion. If the file does not exist, new rates will be
|
46
|
+
downloaded from the European Central Bank and stored in the file. To update the
|
47
|
+
cache,
|
48
|
+
|
49
|
+
.. code-block:: ruby
|
50
|
+
|
51
|
+
Money.default_bank.update
|
52
|
+
|
53
|
+
|
54
|
+
Rounding
|
55
|
+
--------
|
56
|
+
|
57
|
+
By default, ``Money::Bank``'s will truncate. If you prefer to round, exchange
|
58
|
+
methods will accept a block that will be yielded; that block is intended for
|
59
|
+
rounding when exchanging (continuing from above):
|
60
|
+
|
61
|
+
.. code-block:: ruby
|
62
|
+
|
63
|
+
puts '1 EUR'.to_money.exchange_to(:USD) {|x| x.round}
|
64
|
+
|
65
|
+
|
66
|
+
Auto-update rates
|
67
|
+
-----------------
|
68
|
+
|
69
|
+
The European Central Bank publishes daily foreign exchange rates every day, and
|
70
|
+
they should be available at 14:00 CET. To automatically update the cache, set
|
71
|
+
``Money::Bank::ECB#auto_update = true``.
|
72
|
+
|
73
|
+
|
74
|
+
Contribute
|
75
|
+
----------
|
76
|
+
|
77
|
+
* `Fork <https://github.com/ct-clearhaus/money-ecb/fork>`_
|
78
|
+
* Clone
|
79
|
+
* ``bundle install``
|
80
|
+
* ``bundle exec rake test``
|
81
|
+
* Make your changes
|
82
|
+
* Test your changes
|
83
|
+
* Create a Pull Request and check `Travis
|
84
|
+
<https://travis-ci.org/ct-clearhaus/money-ecb/pull_requests>`_
|
85
|
+
* Enjoy!
|
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'money'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'bigdecimal'
|
5
|
+
require 'zip'
|
6
|
+
require 'csv'
|
7
|
+
|
8
|
+
class Money
|
9
|
+
module Bank
|
10
|
+
class ECBInvalidCache < StandardError; end
|
11
|
+
|
12
|
+
class ECB < Money::Bank::VariableExchange
|
13
|
+
RATES_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip'
|
14
|
+
|
15
|
+
def initialize(cache_filename, &rounding_method)
|
16
|
+
@cache_filename = cache_filename
|
17
|
+
setup
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :auto_update
|
21
|
+
|
22
|
+
def setup
|
23
|
+
super
|
24
|
+
load_from_cachefile rescue update
|
25
|
+
end
|
26
|
+
|
27
|
+
def exchange_with(from, to, &rounding_method)
|
28
|
+
update if @auto_update and Time.now.utc > (@rates_date + 60*60*24)
|
29
|
+
super(from, to, &rounding_method)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update
|
33
|
+
update_cachefile
|
34
|
+
load_from_cachefile
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :currencies
|
38
|
+
attr_reader :rates_date
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def update_cachefile
|
43
|
+
File.open(@cache_filename, 'w') do |cache_file|
|
44
|
+
Zip::InputStream.open(open(RATES_URL)) do |io|
|
45
|
+
io.get_next_entry
|
46
|
+
cache_file.puts(io.read)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Load date and rates from the cache file.
|
52
|
+
#
|
53
|
+
# Be "loose" to accommodate for future changes in list of currencies etc.
|
54
|
+
def load_from_cachefile
|
55
|
+
csv = CSV.parse(File.open(@cache_filename).read, :headers => true)
|
56
|
+
|
57
|
+
pairs = Hash[csv.first.map{|x,y| [x.strip, y.strip]}]
|
58
|
+
pairs.delete('')
|
59
|
+
date_s = pairs.delete('Date')
|
60
|
+
|
61
|
+
@mutex.synchronize do
|
62
|
+
@rates_date = Time.parse(date_s + ' 14:00:00 UTC')
|
63
|
+
|
64
|
+
quotations = Hash[pairs.map{|cur,rate| [cur, BigDecimal.new(rate)]}]
|
65
|
+
quotations.delete('')
|
66
|
+
|
67
|
+
@currencies = quotations.keys
|
68
|
+
|
69
|
+
quotations.each do |currency, rate|
|
70
|
+
set_rate('EUR', currency, rate, :without_mutex => true)
|
71
|
+
set_rate(currency, 'EUR', 1/rate, :without_mutex => true)
|
72
|
+
|
73
|
+
quotations.each do |other_currency, other_rate|
|
74
|
+
next if currency == other_currency
|
75
|
+
set_rate(currency, other_currency, rate/other_rate, :without_mutex => true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/money-ecb.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'money-ecb'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.summary = 'Foreign exchange rates from the EU Central Bank (ECB).'
|
5
|
+
s.description = 'A Money::Bank that will fetch foreign exchange rates from the EU Central Bank (ECB).'
|
6
|
+
s.authors = ['Casper Thomsen']
|
7
|
+
s.email = 'ct@clearhaus.com'
|
8
|
+
s.homepage = 'https://github.com/ct-clearhaus/money-ecb'
|
9
|
+
s.files = `git ls-files`.split($/)
|
10
|
+
|
11
|
+
s.add_runtime_dependency('money', '~> 6.0.0')
|
12
|
+
s.add_runtime_dependency('rubyzip', '>= 1.0.0')
|
13
|
+
end
|
Binary file
|
@@ -0,0 +1,2 @@
|
|
1
|
+
Date, USD, JPY, BGN, CZK, DKK, GBP, HUF, LTL, PLN, RON, SEK, CHF, NOK, HRK, RUB, TRY, AUD, BRL, CAD, CNY, HKD, IDR, INR, KRW, MXN, MYR, NZD, PHP, SGD, THB, ZAR, ILS,
|
2
|
+
30 January 2014, 1.3574, 139.28, 1.9558, 27.594, 7.4622, 0.82380, 310.97, 3.4528, 4.2312, 4.5110, 8.8347, 1.2233, 8.4680, 7.6605, 47.8025, 3.0808, 1.5459, 3.2955, 1.5176, 8.2302, 10.5421, 16551.39, 85.0840, 1469.53, 18.1111, 4.5417, 1.6624, 61.527, 1.7323, 44.745, 15.2700, 4.7416,
|
data/spec/ecb_spec.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'money/bank/ecb'
|
2
|
+
|
3
|
+
describe 'ECB' do
|
4
|
+
before do
|
5
|
+
@assetsdir = File.dirname(__FILE__) + '/assets'
|
6
|
+
@tmpdir = File.dirname(__FILE__) + '/tmp'
|
7
|
+
%x{cp -r #{@assetsdir} #{@tmpdir}}
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:bank) { Money::Bank::ECB.new(@tmpdir + '/good_rates.csv') }
|
11
|
+
|
12
|
+
after do
|
13
|
+
%x{rm -rf #{@tmpdir}}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#currencies' do
|
17
|
+
subject(:currencies) { bank.currencies }
|
18
|
+
|
19
|
+
it 'should have 32 currencies' do
|
20
|
+
expect(currencies.length).to eq(32)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#exchange_with' do
|
25
|
+
let(:good_rates) do
|
26
|
+
{
|
27
|
+
'USD' => 1.3574, 'JPY' => 139.28, 'BGN' => 1.9558, 'CZK' => 27.594,
|
28
|
+
'DKK' => 7.4622, 'GBP' => 0.82380, 'HUF' => 310.97, 'LTL' => 3.4528,
|
29
|
+
'PLN' => 4.2312, 'RON' => 4.5110, 'SEK' => 8.8347, 'CHF' => 1.2233,
|
30
|
+
'NOK' => 8.4680, 'HRK' => 7.6605, 'RUB' => 47.8025, 'TRY' => 3.0808,
|
31
|
+
'AUD' => 1.5459, 'BRL' => 3.2955, 'CAD' => 1.5176, 'CNY' => 8.2302,
|
32
|
+
'HKD' => 10.5421, 'IDR' => 16551.39, 'INR' => 85.0840, 'KRW' => 1469.53,
|
33
|
+
'MXN' => 18.1111, 'MYR' => 4.5417, 'NZD' => 1.6624, 'PHP' => 61.527,
|
34
|
+
'SGD' => 1.7323, 'THB' => 44.745, 'ZAR' => 15.2700, 'ILS' => 4.7416,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def fx(cents, from, to)
|
39
|
+
bank.exchange_with(Money.new(cents, from), to) {|x| x.floor}
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should exchange correctly from EUR' do
|
43
|
+
bank.currencies.each do |cur|
|
44
|
+
sub2u = Money::Currency.wrap(cur).subunit_to_unit
|
45
|
+
|
46
|
+
expect(fx(100, 'EUR', cur).cents).to eq((good_rates[cur]*sub2u).floor)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should exchange correctly to EUR' do
|
51
|
+
bank.currencies.each do |cur|
|
52
|
+
sub2u = Money::Currency.wrap(cur).subunit_to_unit
|
53
|
+
|
54
|
+
factor = 1000 # To ensure non-zero values.
|
55
|
+
expect(fx(factor*sub2u, cur, 'EUR').cents).to eq((factor*1/good_rates[cur]*100).floor)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should exchange correctly between non-EUR currencies'
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#update' do
|
63
|
+
it 'should update rates from ECB' do
|
64
|
+
expect(bank).to receive(:open).with(Money::Bank::ECB::RATES_URL).and_return(
|
65
|
+
File.expand_path(@tmpdir + '/eurofxref.zip'))
|
66
|
+
|
67
|
+
expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
|
68
|
+
bank.update
|
69
|
+
expect(bank.rates_date).to eq(Time.utc(2014, 01, 31, 14))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#new' do
|
74
|
+
context 'when cache file is good' do
|
75
|
+
it 'should fetch use rates from cache'
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when cache file is bogus' do
|
79
|
+
it 'should fetch rates from ECB'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#auto_update' do
|
84
|
+
it 'should be off by default' do
|
85
|
+
expect(bank.auto_update).to be_nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when auto_update is on' do
|
90
|
+
before(:each) { bank.auto_update = true }
|
91
|
+
|
92
|
+
context 'and cache file is new enough' do
|
93
|
+
describe '#exchange_with' do
|
94
|
+
it 'should not update cache' do
|
95
|
+
expect(Time).to receive(:now).and_return(bank.rates_date + 60*60*23)
|
96
|
+
expect(bank).not_to receive(:update)
|
97
|
+
bank.exchange_with(Money.new(100, :EUR), :USD)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'and cache file is old' do
|
103
|
+
describe '#exchange_with' do
|
104
|
+
it 'should update cache' do
|
105
|
+
expect(Time).to receive(:now).and_return(bank.rates_date + 60*60*25)
|
106
|
+
expect(bank).to receive(:update)
|
107
|
+
bank.exchange_with(Money.new(100, :EUR), :USD)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: money-ecb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Casper Thomsen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: money
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 6.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 6.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rubyzip
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
description: A Money::Bank that will fetch foreign exchange rates from the EU Central
|
47
|
+
Bank (ECB).
|
48
|
+
email: ct@clearhaus.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .travis.yml
|
55
|
+
- Gemfile
|
56
|
+
- README.rst
|
57
|
+
- Rakefile
|
58
|
+
- lib/money/bank/ecb.rb
|
59
|
+
- money-ecb.gemspec
|
60
|
+
- spec/assets/eurofxref.zip
|
61
|
+
- spec/assets/good_rates.csv
|
62
|
+
- spec/ecb_spec.rb
|
63
|
+
homepage: https://github.com/ct-clearhaus/money-ecb
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.25
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Foreign exchange rates from the EU Central Bank (ECB).
|
87
|
+
test_files: []
|
88
|
+
has_rdoc:
|