money-ecb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.swp
2
+ .bundle
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ install: bundle install --without development
7
+ script: bundle exec rake test
8
+ notifications:
9
+ email: false
10
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'money'
4
+ gem 'rubyzip'
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ gem 'rake'
9
+ end
10
+
11
+ group :development do
12
+ gem 'pry'
13
+ end
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,9 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ task :default => :test
4
+
5
+ desc 'Run all specs'
6
+ RSpec::Core::RakeTask.new(:test) do |task|
7
+ task.pattern = './spec/**/*_spec.rb'
8
+ task.rspec_opts = '-fs --color'.split
9
+ end
@@ -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: