money-ecb 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/README.rst +51 -22
- data/lib/money/bank/ecb.rb +171 -26
- data/money-ecb.gemspec +4 -3
- data/spec/assets/bogus_rates.csv +2 -0
- data/spec/ecb_spec.rb +108 -18
- data/spec/support/custom_expectations/write_expectation.rb +75 -0
- metadata +21 -12
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTJmZTg5YTgzNDFjOGI5ZjVmNmI4NGMzMzg3MGRiNzJmYTM3NGMyZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGIwZjA4OTkxYTkwNjZhNjBkZWYwZTExYzdhMjg0NTJmZWM4MWRmMw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZGJhZjMwOTRiZTI1YjhkYTBmNTcyY2M2NDAwYjY5OThhZmRkMjg3OTA5YTE4
|
10
|
+
MjBkNGNhZGU1MTQyMWY2ZjliOThlY2Y5YTE0NjExYWU3MzM2OTU5YWZhNWY4
|
11
|
+
ZTg2NGQxNTc4YWQ0MTQyMDEwODE0MDhhOWJjYmI1OTM4OGE3ZjQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OThmOTk0ZThmMWI1YzFkYmVlZWFiNTE2YjFkY2QyNjUyZTUyY2NiOGE4ZjA4
|
14
|
+
Y2Q4OGY4ZGIwYmQ0OGYwZjdlZGMyZjI4MDk5ZGM2NGJjYzE5YmY2YTM0YjQ4
|
15
|
+
Yzc5NGNhMGJjMGJlMWU5NmI3ODZjMmMxNjgwNDFlYWJkNTdiNmI=
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.rst
CHANGED
@@ -1,16 +1,32 @@
|
|
1
|
+
.. role:: ruby(code)
|
2
|
+
:language: ruby
|
3
|
+
|
4
|
+
.. role:: sh(code)
|
5
|
+
:language: sh
|
6
|
+
|
1
7
|
money-ecb
|
2
8
|
==========
|
3
9
|
|
4
10
|
.. image:: https://travis-ci.org/ct-clearhaus/money-ecb.png?branch=master
|
11
|
+
:alt: Build Status
|
5
12
|
:target: https://travis-ci.org/ct-clearhaus/money-ecb
|
6
13
|
|
14
|
+
.. image:: https://codeclimate.com/github/ct-clearhaus/money-ecb.png
|
15
|
+
:alt: Code Climate
|
16
|
+
:target: https://codeclimate.com/github/ct-clearhaus/money-ecb
|
17
|
+
|
18
|
+
.. image:: https://gemnasium.com/ct-clearhaus/money-ecb.png
|
19
|
+
:alt: Dependency Status
|
20
|
+
:target: https://gemnasium.com/ct-clearhaus/money-ecb
|
21
|
+
|
7
22
|
|
8
23
|
Introduction
|
9
24
|
------------
|
10
25
|
|
11
|
-
This gem is a
|
12
|
-
ECB (European Central Bank).
|
26
|
+
This gem is a RubyMoney_ bank that can exchange ``Money`` using rates from the
|
27
|
+
ECB (European Central Bank). It will automatically keep the rates updated.
|
13
28
|
|
29
|
+
.. _RubyMoney: http://rubymoney.github.io/money
|
14
30
|
|
15
31
|
Installation
|
16
32
|
------------
|
@@ -19,6 +35,9 @@ Installation
|
|
19
35
|
|
20
36
|
gem install money-ecb
|
21
37
|
|
38
|
+
In your ``Gemfile`` may want to have :ruby:`gem 'money-ecb', :require =>
|
39
|
+
'money/bank/ecb'`.
|
40
|
+
|
22
41
|
Dependencies
|
23
42
|
............
|
24
43
|
|
@@ -37,38 +56,48 @@ Using ``money`` and ``monetize``:
|
|
37
56
|
require 'money/bank/ecb'
|
38
57
|
require 'monetize/core_extensions'
|
39
58
|
|
40
|
-
Money.default_bank = Money::Bank::ECB.new('/tmp/ecb
|
59
|
+
Money.default_bank = Money::Bank::ECB.new('/tmp/ecb.cache')
|
41
60
|
|
42
61
|
puts '1 EUR'.to_money.exchange_to(:USD)
|
43
62
|
|
44
|
-
If ``/tmp/ecb
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
.. code-block:: ruby
|
50
|
-
|
51
|
-
Money.default_bank.update
|
63
|
+
If ``/tmp/ecb.cache`` is a valid CSV file with exchange rates, the rates will be
|
64
|
+
used for conversion (unless newer rates are available; if so, new rates will be
|
65
|
+
fetched—see `auto-update`_). If the file does not exist or is "somehow bogus",
|
66
|
+
new rates will be downloaded from the European Central Bank and stored in the
|
67
|
+
file.
|
52
68
|
|
53
69
|
|
54
70
|
Rounding
|
55
71
|
--------
|
56
72
|
|
57
|
-
By default, ``Money::Bank``'s will truncate. If you prefer to round
|
58
|
-
methods will accept a block that will be yielded; that block is intended for
|
59
|
-
rounding when exchanging (continuing from above):
|
73
|
+
By default, ``Money::Bank``'s will truncate. If you prefer to round:
|
60
74
|
|
61
75
|
.. code-block:: ruby
|
62
76
|
|
63
77
|
puts '1 EUR'.to_money.exchange_to(:USD) {|x| x.round}
|
64
78
|
|
79
|
+
If you would like to have rounding done by default, you can set the default when
|
80
|
+
creating the ``ECB`` instance:
|
81
|
+
|
82
|
+
.. code-block:: ruby
|
83
|
+
|
84
|
+
Money.default_bank = Money::Bank::ECB.new('/tmp/ecb.cache') {|x| x.round}
|
85
|
+
|
86
|
+
|
87
|
+
.. _`auto-update`:
|
65
88
|
|
66
89
|
Auto-update rates
|
67
90
|
-----------------
|
68
91
|
|
69
|
-
The European Central Bank publishes
|
70
|
-
|
71
|
-
|
92
|
+
The European Central Bank publishes foreign exchange rates daily, and they
|
93
|
+
should be available at 14:00 CE(S)T. The cache is automatically updated when
|
94
|
+
doing an exchange after new rates has been published; to disable this, set
|
95
|
+
:ruby:`Money::Bank::ECB#auto_update = false`.
|
96
|
+
|
97
|
+
Also notice that when instantiating an ``ECB`` rates will be loaded from the
|
98
|
+
cache file, and if that fails, new rates will be fetched automatically. So if
|
99
|
+
you want to handle updating rates "by hand", you should place a valid cache
|
100
|
+
before ``ECB.new`` and then call ``#reload`` after you updated the cache.
|
72
101
|
|
73
102
|
|
74
103
|
Contribute
|
@@ -76,10 +105,10 @@ Contribute
|
|
76
105
|
|
77
106
|
* `Fork <https://github.com/ct-clearhaus/money-ecb/fork>`_
|
78
107
|
* Clone
|
79
|
-
*
|
80
|
-
* ``bundle exec rake test``
|
108
|
+
* :sh:`bundle install && bundle exec rake test`
|
81
109
|
* Make your changes
|
82
|
-
*
|
83
|
-
|
84
|
-
|
110
|
+
* :sh:`bundle exec rake test` again, preferably against Ruby 1.9.3, 2.0.0 and
|
111
|
+
2.1.0 (`Travis <https://travis-ci.org/ct-clearhaus/money-ecb/pull_requests>`_
|
112
|
+
will do that).
|
113
|
+
* Create a Pull Request
|
85
114
|
* Enjoy!
|
data/lib/money/bank/ecb.rb
CHANGED
@@ -7,63 +7,131 @@ require 'csv'
|
|
7
7
|
|
8
8
|
class Money
|
9
9
|
module Bank
|
10
|
-
class
|
11
|
-
|
10
|
+
# This class represents the European Central Bank or more precisely, its
|
11
|
+
# foreign exchange rates.
|
12
|
+
#
|
13
|
+
# @!attribute [r] currencies
|
14
|
+
# The available currencies to exchange between.
|
15
|
+
# @return [Array<String>]
|
16
|
+
#
|
17
|
+
# @!attribute [r] rounding_method
|
18
|
+
# The default rounding method for the bank.
|
19
|
+
# @return [Proc]
|
20
|
+
#
|
21
|
+
# @!attribute [rw] auto_update
|
22
|
+
# Auto-updating on or off.
|
23
|
+
# @param [true,false] bool
|
24
|
+
# @return [true,false]
|
25
|
+
#
|
26
|
+
# @!attribute [r] cache_filename
|
27
|
+
# The cache path and file name
|
28
|
+
# @return[String]
|
29
|
+
#
|
30
|
+
# @!attribute [r] rates_date
|
31
|
+
# The time on which ECB published the rates that is currently in use.
|
32
|
+
# @return[Time]
|
12
33
|
class ECB < Money::Bank::VariableExchange
|
34
|
+
# Error thrown when the cache file to be loaded isn't "good enough".
|
35
|
+
class InvalidCacheError < StandardError; end
|
36
|
+
|
13
37
|
RATES_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip'
|
14
38
|
|
39
|
+
# Creates an Money::Bank::ECB.
|
40
|
+
#
|
41
|
+
# @param [String] cache_filename Absolute path and filename to the cache.
|
42
|
+
#
|
43
|
+
# @yield [x] Optional block to use for rounding after exchanging.
|
44
|
+
# @yieldparam [Float] x The exchanged amount of cents to be rounded.
|
45
|
+
# @yieldreturn [Integer]
|
46
|
+
#
|
47
|
+
# @return [Money::Bank::ECB]
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# Money::Bank::ECB.new('/tmp/ecb.cache') {|x| x.floor}
|
51
|
+
# #=> #<Money::Bank::ECB
|
52
|
+
# @cache_filename='/tmp/ecb.cache',
|
53
|
+
# @rounding_method=#<Proc>,
|
54
|
+
# @auto_update=true,
|
55
|
+
# @rates=#<Hash>,
|
56
|
+
# @rates_date=#<Time>,
|
57
|
+
# @currencies=#<Array>>
|
15
58
|
def initialize(cache_filename, &rounding_method)
|
16
|
-
@cache_filename
|
59
|
+
@cache_filename = cache_filename
|
60
|
+
@rounding_method = rounding_method
|
17
61
|
setup
|
18
62
|
end
|
19
63
|
|
64
|
+
attr_reader :cache_filename
|
65
|
+
attr_reader :rounding_method
|
20
66
|
attr_accessor :auto_update
|
21
67
|
|
68
|
+
# Setup rates hash, mutex for rates locking, and auto_update default.
|
69
|
+
#
|
70
|
+
# @return [self]
|
22
71
|
def setup
|
72
|
+
@auto_update = true
|
23
73
|
super
|
24
|
-
|
74
|
+
reload rescue update
|
25
75
|
end
|
26
76
|
|
77
|
+
# Exchange some money
|
78
|
+
#
|
79
|
+
# @param [Money] from To exchange from.
|
80
|
+
# @param [String,Symbol] to Currency to exchange to.
|
81
|
+
# @yield [x] Optional block to use for rounding after exchanging.
|
82
|
+
# @yieldparam [Float] x The exchanged amount of cents to be rounded.
|
83
|
+
# @yieldreturn [Integer]
|
84
|
+
#
|
85
|
+
# @return [Money]
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# ecb.exchange_with(Money.new(100, :EUR), :USD)
|
89
|
+
# #=> #<Money
|
27
90
|
def exchange_with(from, to, &rounding_method)
|
28
91
|
update if @auto_update and Time.now.utc > (@rates_date + 60*60*24)
|
29
92
|
super(from, to, &rounding_method)
|
30
93
|
end
|
31
94
|
|
95
|
+
# Update the cache file and load the new rates.
|
96
|
+
#
|
97
|
+
# This is only relevant if #auto_update is false.
|
98
|
+
#
|
99
|
+
# @return [self]
|
32
100
|
def update
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
attr_reader :currencies
|
38
|
-
attr_reader :rates_date
|
101
|
+
update_cache
|
102
|
+
reload
|
39
103
|
|
40
|
-
|
104
|
+
self
|
105
|
+
end
|
41
106
|
|
42
|
-
|
107
|
+
# Update the cache.
|
108
|
+
#
|
109
|
+
# @return [self]
|
110
|
+
def update_cache
|
43
111
|
File.open(@cache_filename, 'w') do |cache_file|
|
44
112
|
Zip::InputStream.open(open(RATES_URL)) do |io|
|
45
113
|
io.get_next_entry
|
46
114
|
cache_file.puts(io.read)
|
47
115
|
end
|
48
116
|
end
|
117
|
+
|
118
|
+
self
|
49
119
|
end
|
50
120
|
|
51
|
-
|
121
|
+
attr_reader :currencies
|
122
|
+
attr_reader :rates_date
|
123
|
+
|
124
|
+
# Load rates from the cache file.
|
52
125
|
#
|
53
126
|
# Be "loose" to accommodate for future changes in list of currencies etc.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
date_s = pairs.delete('Date')
|
127
|
+
#
|
128
|
+
# @raise [InvalidCacheError] if cache is bogus.
|
129
|
+
# @return [self]
|
130
|
+
def reload
|
131
|
+
time, quotations = cache_content
|
60
132
|
|
61
133
|
@mutex.synchronize do
|
62
|
-
@rates_date =
|
63
|
-
|
64
|
-
quotations = Hash[pairs.map{|cur,rate| [cur, BigDecimal.new(rate)]}]
|
65
|
-
quotations.delete('')
|
66
|
-
|
134
|
+
@rates_date = time
|
67
135
|
@currencies = quotations.keys
|
68
136
|
|
69
137
|
quotations.each do |currency, rate|
|
@@ -72,12 +140,89 @@ class Money
|
|
72
140
|
|
73
141
|
quotations.each do |other_currency, other_rate|
|
74
142
|
next if currency == other_currency
|
75
|
-
|
143
|
+
|
144
|
+
set_rate(currency, other_currency, other_rate/rate, :without_mutex => true)
|
76
145
|
end
|
77
146
|
end
|
78
147
|
end
|
79
148
|
|
80
|
-
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# Read the cache and understand it.
|
155
|
+
#
|
156
|
+
# @return [[Time, Hash]]
|
157
|
+
def cache_content
|
158
|
+
csv = CSV.parse(File.open(@cache_filename).read, :headers => true)
|
159
|
+
csv = csv.first # Only one line.
|
160
|
+
|
161
|
+
# Clean-up
|
162
|
+
hash = Hash[csv.map{|x,y| [x.strip, y.strip]}]
|
163
|
+
hash.delete('')
|
164
|
+
|
165
|
+
date = hash.delete('Date')
|
166
|
+
|
167
|
+
time = Time.parse(date + ' ' + self.class.new_rates_time_of_day_s(date))
|
168
|
+
quotations = Hash[hash.map{|cur,rate| [cur, BigDecimal.new(rate)]}]
|
169
|
+
|
170
|
+
[time, quotations]
|
171
|
+
rescue
|
172
|
+
raise InvalidCacheError
|
173
|
+
end
|
174
|
+
|
175
|
+
# Time of day (as a string ready for Time.parse) at which ECB publishes
|
176
|
+
# new rates.
|
177
|
+
#
|
178
|
+
# @return [String] Either '13:00:00 UTC' or '14:00:00 UTC' depending on
|
179
|
+
# DST.
|
180
|
+
def self.new_rates_time_of_day_s(date)
|
181
|
+
# FIXME: It should be off by one day!
|
182
|
+
|
183
|
+
# 15:00 ECB local time = 14:00 UTC when CET, 13:00 UTC when CEST.
|
184
|
+
#
|
185
|
+
# CEST:
|
186
|
+
# - Starts in the morning of the last Sunday in March.
|
187
|
+
# - Ends in the morning of the last Sunday in October.
|
188
|
+
#
|
189
|
+
# March ...25 26 27 28 29 30 31
|
190
|
+
#
|
191
|
+
# [Su] Mo Tu We Th Fr Sa
|
192
|
+
# Sa [Su] Mo Tu We Th Fr
|
193
|
+
# Fr Sa [Su] Mo Tu We Th
|
194
|
+
# Th Fr Sa [Su] Mo Tu We
|
195
|
+
# We Th Fr Sa [Su] Mo Tu
|
196
|
+
# Tu We Th Fr Sa [Su] Mo
|
197
|
+
# Mo Tu We Th Fr Sa [Su]
|
198
|
+
#
|
199
|
+
# October...25 26 27 28 29 30 31
|
200
|
+
#
|
201
|
+
# Where are we relative to the Sunday-diagonal?
|
202
|
+
|
203
|
+
time = Time.parse(date)
|
204
|
+
|
205
|
+
on = time.day >= 25 && time.sunday?
|
206
|
+
above =
|
207
|
+
(time.day == 26 && [1].include?(time.wday)) ||
|
208
|
+
(time.day == 27 && [2,1].include?(time.wday)) ||
|
209
|
+
(time.day == 28 && [3,2,1].include?(time.wday)) ||
|
210
|
+
(time.day == 29 && [4,3,2,1].include?(time.wday)) ||
|
211
|
+
(time.day == 30 && [5,4,3,2,1].include?(time.wday)) ||
|
212
|
+
(time.day == 31 && [6,5,4,3,2,1].include?(time.wday))
|
213
|
+
below =
|
214
|
+
(time.day == 25 && [6,5,4,3,2,1].include?(time.wday)) ||
|
215
|
+
(time.day == 26 && [6,5,4,3,2].include?(time.wday)) ||
|
216
|
+
(time.day == 27 && [6,5,4,3].include?(time.wday)) ||
|
217
|
+
(time.day == 28 && [6,5,4].include?(time.wday)) ||
|
218
|
+
(time.day == 29 && [6,5].include?(time.wday)) ||
|
219
|
+
(time.day == 30 && [6].include?(time.wday))
|
220
|
+
|
221
|
+
cest = (4..9).include?(time.month) ||
|
222
|
+
(time.month == 3 && (on || above)) ||
|
223
|
+
(time.month == 10 && (on || below))
|
224
|
+
|
225
|
+
cest ? '13:00:00 UTC' : '14:00:00 UTC'
|
81
226
|
end
|
82
227
|
end
|
83
228
|
end
|
data/money-ecb.gemspec
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'money-ecb'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.2'
|
4
4
|
s.summary = 'Foreign exchange rates from the EU Central Bank (ECB).'
|
5
5
|
s.description = 'A Money::Bank that will fetch foreign exchange rates from the EU Central Bank (ECB).'
|
6
|
+
s.license = 'MIT'
|
6
7
|
s.authors = ['Casper Thomsen']
|
7
8
|
s.email = 'ct@clearhaus.com'
|
8
9
|
s.homepage = 'https://github.com/ct-clearhaus/money-ecb'
|
9
10
|
s.files = `git ls-files`.split($/)
|
10
11
|
|
11
|
-
s.add_runtime_dependency
|
12
|
-
s.add_runtime_dependency
|
12
|
+
s.add_runtime_dependency 'money', '~> 6.0', '>= 6.0.0'
|
13
|
+
s.add_runtime_dependency 'rubyzip', '~> 1.0', '>= 1.0.0'
|
13
14
|
end
|
@@ -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
|
+
41 December 2013, 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
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'money/bank/ecb'
|
2
|
+
require_relative 'support/custom_expectations/write_expectation'
|
2
3
|
|
3
4
|
describe 'ECB' do
|
4
5
|
before do
|
@@ -7,7 +8,12 @@ describe 'ECB' do
|
|
7
8
|
%x{cp -r #{@assetsdir} #{@tmpdir}}
|
8
9
|
end
|
9
10
|
|
10
|
-
let(:bank)
|
11
|
+
let(:bank) do
|
12
|
+
bank = Money::Bank::ECB.new(@tmpdir + '/good_rates.csv')
|
13
|
+
bank.auto_update = false
|
14
|
+
|
15
|
+
bank
|
16
|
+
end
|
11
17
|
|
12
18
|
after do
|
13
19
|
%x{rm -rf #{@tmpdir}}
|
@@ -40,49 +46,133 @@ describe 'ECB' do
|
|
40
46
|
end
|
41
47
|
|
42
48
|
it 'should exchange correctly from EUR' do
|
43
|
-
bank.currencies.each do |
|
44
|
-
|
49
|
+
bank.currencies.each do |to_cur|
|
50
|
+
from_cents = 100
|
51
|
+
to_cents = Money::Currency.wrap(to_cur).subunit_to_unit * good_rates[to_cur]
|
45
52
|
|
46
|
-
expect(fx(
|
53
|
+
expect(fx(from_cents, 'EUR', to_cur).cents).to eq(to_cents.floor)
|
47
54
|
end
|
48
55
|
end
|
49
56
|
|
50
57
|
it 'should exchange correctly to EUR' do
|
51
|
-
|
52
|
-
|
58
|
+
factor = 1000 # To ensure non-zero values.
|
59
|
+
|
60
|
+
bank.currencies.each do |from_cur|
|
61
|
+
from_cents = factor * Money::Currency.wrap(from_cur).subunit_to_unit
|
62
|
+
|
63
|
+
rate = 1/good_rates[from_cur]
|
64
|
+
to_cents = factor * 100 * rate
|
53
65
|
|
54
|
-
|
55
|
-
expect(fx(factor*sub2u, cur, 'EUR').cents).to eq((factor*1/good_rates[cur]*100).floor)
|
66
|
+
expect(fx(from_cents, from_cur, 'EUR').cents).to eq(to_cents.floor)
|
56
67
|
end
|
57
68
|
end
|
58
69
|
|
59
|
-
it 'should exchange correctly between non-EUR currencies'
|
70
|
+
it 'should exchange correctly between non-EUR currencies' do
|
71
|
+
factor = 1000 # To ensure non-zero values.
|
72
|
+
|
73
|
+
bank.currencies.each do |from_cur|
|
74
|
+
from_cents = factor * Money::Currency.wrap(from_cur).subunit_to_unit
|
75
|
+
|
76
|
+
bank.currencies.each do |to_cur|
|
77
|
+
next if from_cur == to_cur
|
78
|
+
|
79
|
+
rate = good_rates[to_cur]/good_rates[from_cur]
|
80
|
+
to_cents = factor * Money::Currency.wrap(to_cur).subunit_to_unit * rate
|
81
|
+
|
82
|
+
expect(fx(from_cents, from_cur, to_cur).cents).to eq(to_cents.floor) rescue binding.pry
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
60
86
|
end
|
61
87
|
|
62
|
-
describe '#
|
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'))
|
88
|
+
describe '#update_cache and #reload' do
|
89
|
+
it 'should update rates from ECB and should load rates from the cache file, resp.' do
|
90
|
+
expect(bank).to receive(:open).with(Money::Bank::ECB::RATES_URL).and_return(File.expand_path(@tmpdir + '/eurofxref.zip'))
|
66
91
|
|
67
92
|
expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
|
68
|
-
bank.
|
93
|
+
bank.update_cache
|
94
|
+
expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
|
95
|
+
bank.reload
|
69
96
|
expect(bank.rates_date).to eq(Time.utc(2014, 01, 31, 14))
|
70
97
|
end
|
71
98
|
end
|
72
99
|
|
100
|
+
describe '#update' do
|
101
|
+
it 'should do #update_cache and #reload' do
|
102
|
+
expect(bank).to receive(:update_cache)
|
103
|
+
expect(bank).to receive(:reload)
|
104
|
+
|
105
|
+
bank.update
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
73
109
|
describe '#new' do
|
110
|
+
before(:each) { bank.auto_update = false }
|
111
|
+
|
112
|
+
let(:good_rates) do
|
113
|
+
{
|
114
|
+
'USD' => 1.3574, 'JPY' => 139.28, 'BGN' => 1.9558, 'CZK' => 27.594,
|
115
|
+
'DKK' => 7.4622, 'GBP' => 0.82380, 'HUF' => 310.97, 'LTL' => 3.4528,
|
116
|
+
'PLN' => 4.2312, 'RON' => 4.5110, 'SEK' => 8.8347, 'CHF' => 1.2233,
|
117
|
+
'NOK' => 8.4680, 'HRK' => 7.6605, 'RUB' => 47.8025, 'TRY' => 3.0808,
|
118
|
+
'AUD' => 1.5459, 'BRL' => 3.2955, 'CAD' => 1.5176, 'CNY' => 8.2302,
|
119
|
+
'HKD' => 10.5421, 'IDR' => 16551.39, 'INR' => 85.0840, 'KRW' => 1469.53,
|
120
|
+
'MXN' => 18.1111, 'MYR' => 4.5417, 'NZD' => 1.6624, 'PHP' => 61.527,
|
121
|
+
'SGD' => 1.7323, 'THB' => 44.745, 'ZAR' => 15.2700, 'ILS' => 4.7416,
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
74
125
|
context 'when cache file is good' do
|
75
|
-
it 'should
|
126
|
+
it 'should use rates from cache' do
|
127
|
+
expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
|
128
|
+
bank.currencies.each do |cur|
|
129
|
+
expect(bank.rates['EUR_TO_' + cur]).to eq(good_rates[cur])
|
130
|
+
end
|
131
|
+
end
|
76
132
|
end
|
77
133
|
|
78
134
|
context 'when cache file is bogus' do
|
79
|
-
it 'should fetch rates from ECB'
|
135
|
+
it 'should fetch rates from ECB' do
|
136
|
+
expect{
|
137
|
+
Money::Bank::ECB::RATES_URL = File.expand_path(@tmpdir + '/eurofxref.zip')
|
138
|
+
}.to write(/warning: already initialized constant .*RATES_URL/).to(:stderr)
|
139
|
+
|
140
|
+
bogus = Money::Bank::ECB.new(@tmpdir + '/bogus_rates.csv')
|
141
|
+
bogus.auto_update = false
|
142
|
+
|
143
|
+
expect(bogus.rates_date).to eq(Time.utc(2014, 01, 31, 14))
|
144
|
+
end
|
80
145
|
end
|
81
146
|
end
|
82
147
|
|
83
148
|
describe '#auto_update' do
|
84
|
-
it 'should be
|
85
|
-
expect(
|
149
|
+
it 'should be on by default' do
|
150
|
+
expect(Money::Bank::ECB.new(@tmpdir + '/good_rates.csv').auto_update).to be_true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe '#cache_content' do
|
155
|
+
context 'when cache file is good' do
|
156
|
+
it 'should return correct date and quotations' do
|
157
|
+
time, quotations = bank.send(:cache_content)
|
158
|
+
|
159
|
+
expect(time).to be_a(Time)
|
160
|
+
expect(time).to eq(Time.utc(2014, 1, 30, 14))
|
161
|
+
expect(quotations).to be_a(Hash)
|
162
|
+
expect(quotations.keys).to include('USD', 'DKK', 'JPY', 'ILS')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'when cache file is bogus' do
|
167
|
+
it 'should raise an InvalidCacheError' do
|
168
|
+
expect(bank.rates_date).to eq(Time.utc(2014, 1, 30, 14))
|
169
|
+
# Make cache bogus.
|
170
|
+
%x{cp #{@tmpdir}/bogus_rates.csv #{@tmpdir}/good_rates.csv}
|
171
|
+
|
172
|
+
expect{
|
173
|
+
bank.send(:cache_content)
|
174
|
+
}.to raise_error(Money::Bank::ECB::InvalidCacheError)
|
175
|
+
end
|
86
176
|
end
|
87
177
|
end
|
88
178
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Source: https://gist.github.com/elgalu/5073871
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
# Custom matcher to test text written to standard output and standard error
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# expect { $stderr.puts "Some random error" }.to write(/Some.* error/).to(:stderr)
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# expect { $stderr.puts "Some specific error" }.to write('Some specific error').to(:stderr)
|
13
|
+
#
|
14
|
+
# @note http://greyblake.com/blog/2012/12/14/custom-expectations-with-rspec/
|
15
|
+
RSpec::Matchers.define :write do |message|
|
16
|
+
chain(:to) do |io|
|
17
|
+
@io = io
|
18
|
+
end
|
19
|
+
|
20
|
+
match do |block|
|
21
|
+
output =
|
22
|
+
case io
|
23
|
+
when :stdout then fake_stdout(&block)
|
24
|
+
when :stderr then fake_stderr(&block)
|
25
|
+
else fail("Allowed values for `to` are :stdout and :stderr, got `#{io.inspect}`")
|
26
|
+
end
|
27
|
+
|
28
|
+
case message
|
29
|
+
when String then output.include? message
|
30
|
+
when Regexp then output.match message
|
31
|
+
else fail("Allowed types for write `message` are String or Regexp, got `#{message.class}`")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
description do
|
36
|
+
%Q[write #{message.inspect} to #{@io}]
|
37
|
+
end
|
38
|
+
|
39
|
+
def failure_message(to = 'to')
|
40
|
+
%Q[expected #{to} #{description} but got #{@buffer.inspect}]
|
41
|
+
end
|
42
|
+
|
43
|
+
failure_message_for_should do
|
44
|
+
failure_message 'to'
|
45
|
+
end
|
46
|
+
|
47
|
+
failure_message_for_should_not do
|
48
|
+
failure_message 'not to'
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fake STDERR and return a string written to it.
|
52
|
+
def fake_stderr
|
53
|
+
original_stderr = $stderr
|
54
|
+
$stderr = StringIO.new
|
55
|
+
yield
|
56
|
+
@buffer = $stderr.string
|
57
|
+
ensure
|
58
|
+
$stderr = original_stderr
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fake STDOUT and return a string written to it.
|
62
|
+
def fake_stdout
|
63
|
+
original_stdout = $stdout
|
64
|
+
$stdout = StringIO.new
|
65
|
+
yield
|
66
|
+
@buffer = $stdout.string
|
67
|
+
ensure
|
68
|
+
$stdout = original_stdout
|
69
|
+
end
|
70
|
+
|
71
|
+
# default IO is standard output
|
72
|
+
def io
|
73
|
+
@io ||= :stdout
|
74
|
+
end
|
75
|
+
end
|
metadata
CHANGED
@@ -1,45 +1,52 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money-ecb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Casper Thomsen
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-06 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: money
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
- - ! '>='
|
20
21
|
- !ruby/object:Gem::Version
|
21
22
|
version: 6.0.0
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
25
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '6.0'
|
30
|
+
- - ! '>='
|
28
31
|
- !ruby/object:Gem::Version
|
29
32
|
version: 6.0.0
|
30
33
|
- !ruby/object:Gem::Dependency
|
31
34
|
name: rubyzip
|
32
35
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
36
|
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
35
40
|
- - ! '>='
|
36
41
|
- !ruby/object:Gem::Version
|
37
42
|
version: 1.0.0
|
38
43
|
type: :runtime
|
39
44
|
prerelease: false
|
40
45
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
46
|
requirements:
|
47
|
+
- - ~>
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.0'
|
43
50
|
- - ! '>='
|
44
51
|
- !ruby/object:Gem::Version
|
45
52
|
version: 1.0.0
|
@@ -57,32 +64,34 @@ files:
|
|
57
64
|
- Rakefile
|
58
65
|
- lib/money/bank/ecb.rb
|
59
66
|
- money-ecb.gemspec
|
67
|
+
- spec/assets/bogus_rates.csv
|
60
68
|
- spec/assets/eurofxref.zip
|
61
69
|
- spec/assets/good_rates.csv
|
62
70
|
- spec/ecb_spec.rb
|
71
|
+
- spec/support/custom_expectations/write_expectation.rb
|
63
72
|
homepage: https://github.com/ct-clearhaus/money-ecb
|
64
|
-
licenses:
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
65
76
|
post_install_message:
|
66
77
|
rdoc_options: []
|
67
78
|
require_paths:
|
68
79
|
- lib
|
69
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
-
none: false
|
71
81
|
requirements:
|
72
82
|
- - ! '>='
|
73
83
|
- !ruby/object:Gem::Version
|
74
84
|
version: '0'
|
75
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
-
none: false
|
77
86
|
requirements:
|
78
87
|
- - ! '>='
|
79
88
|
- !ruby/object:Gem::Version
|
80
89
|
version: '0'
|
81
90
|
requirements: []
|
82
91
|
rubyforge_project:
|
83
|
-
rubygems_version:
|
92
|
+
rubygems_version: 2.2.2
|
84
93
|
signing_key:
|
85
|
-
specification_version:
|
94
|
+
specification_version: 4
|
86
95
|
summary: Foreign exchange rates from the EU Central Bank (ECB).
|
87
96
|
test_files: []
|
88
97
|
has_rdoc:
|