exchange 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -8
- data/Gemfile.lock +12 -6
- data/LICENSE.txt +1 -1
- data/README.rdoc +15 -12
- data/VERSION +1 -1
- data/exchange.gemspec +13 -23
- data/lib/core_extensions/conversability.rb +5 -4
- data/lib/exchange.rb +6 -11
- data/lib/exchange/base.rb +19 -0
- data/lib/exchange/cache/base.rb +57 -32
- data/lib/exchange/cache/file.rb +41 -45
- data/lib/exchange/cache/memcached.rb +28 -29
- data/lib/exchange/cache/no_cache.rb +4 -24
- data/lib/exchange/cache/rails.rb +26 -26
- data/lib/exchange/cache/redis.rb +30 -33
- data/lib/exchange/configuration.rb +167 -73
- data/lib/exchange/currency.rb +61 -30
- data/lib/exchange/external_api.rb +2 -0
- data/lib/exchange/external_api/base.rb +11 -5
- data/lib/exchange/external_api/call.rb +10 -7
- data/lib/exchange/external_api/currency_bot.rb +9 -5
- data/lib/exchange/external_api/ecb.rb +29 -13
- data/lib/exchange/external_api/json.rb +22 -0
- data/lib/exchange/external_api/xavier_media.rb +6 -5
- data/lib/exchange/external_api/xml.rb +22 -0
- data/lib/exchange/gem_loader.rb +35 -0
- data/lib/exchange/helper.rb +22 -15
- data/lib/exchange/iso_4217.rb +55 -44
- data/spec/core_extensions/conversability_spec.rb +2 -2
- data/spec/exchange/cache/base_spec.rb +7 -7
- data/spec/exchange/cache/file_spec.rb +19 -18
- data/spec/exchange/cache/memcached_spec.rb +29 -26
- data/spec/exchange/cache/no_cache_spec.rb +12 -6
- data/spec/exchange/cache/rails_spec.rb +13 -9
- data/spec/exchange/cache/redis_spec.rb +24 -24
- data/spec/exchange/configuration_spec.rb +38 -30
- data/spec/exchange/currency_spec.rb +33 -21
- data/spec/exchange/external_api/base_spec.rb +3 -1
- data/spec/exchange/external_api/call_spec.rb +5 -1
- data/spec/exchange/external_api/currency_bot_spec.rb +5 -1
- data/spec/exchange/external_api/ecb_spec.rb +9 -5
- data/spec/exchange/external_api/xavier_media_spec.rb +5 -1
- data/spec/exchange/gem_loader_spec.rb +29 -0
- data/spec/spec_helper.rb +1 -1
- metadata +12 -87
data/lib/exchange/currency.rb
CHANGED
@@ -8,23 +8,28 @@ module Exchange
|
|
8
8
|
# Currency Objects instantiated from the currency class can be used for basic mathematical operations and currency conversions
|
9
9
|
# @version 0.1
|
10
10
|
# @since 0.1
|
11
|
+
#
|
11
12
|
class Currency
|
12
13
|
include Comparable
|
13
14
|
|
14
15
|
# @attr_reader
|
15
16
|
# @return [BigDecimal] number The number the currency object has been instantiated from
|
17
|
+
#
|
16
18
|
attr_reader :value
|
17
19
|
|
18
20
|
# @attr_reader
|
19
21
|
# @return [Symbol, String] currency the currency of the currency object
|
22
|
+
#
|
20
23
|
attr_reader :currency
|
21
24
|
|
22
25
|
# @attr_reader
|
23
26
|
# @return [Time] The time at which the conversion has taken place or should take place if the object is involved in operations
|
27
|
+
#
|
24
28
|
attr_reader :time
|
25
29
|
|
26
30
|
# @attr_reader
|
27
31
|
# @return [Exchange::Currency] The original currency object this currency object was converted from
|
32
|
+
#
|
28
33
|
attr_reader :from
|
29
34
|
|
30
35
|
# Intialize the currency with a number and a currency
|
@@ -41,7 +46,7 @@ module Exchange
|
|
41
46
|
# @example Instantiate a currency object of 40 US Dollars and convert it to Euro. It shows the conversion date and the original currency
|
42
47
|
# Exchange::Currency.new(40, :usd).to_eur(:at => Time.gm(2012,9,1))
|
43
48
|
# #=> #<Exchange::Currency @number=37.0 @currency=:usd @time=#<Time> @from=#<Exchange::Currency @number=40.0 @currency=:usd>>
|
44
|
-
|
49
|
+
#
|
45
50
|
def initialize value, currency, opts={}
|
46
51
|
@value = ISO4217.instantiate(value, currency)
|
47
52
|
@currency = currency
|
@@ -55,10 +60,10 @@ module Exchange
|
|
55
60
|
# Exchange::Currency.new(40,:usd).to_chf
|
56
61
|
# @example Calls convert_to with 'sek' and :at => Time.gm(2012,2,2)
|
57
62
|
# Exchange::Currency.new(40,:nok).to_sek(:at => Time.gm(2012,2,2))
|
58
|
-
|
63
|
+
#
|
59
64
|
def method_missing method, *args, &block
|
60
65
|
match = method.to_s.match(/\Ato_(\w{3})/)
|
61
|
-
if match &&
|
66
|
+
if match && Exchange.configuration.api.subclass::CURRENCIES.include?($1)
|
62
67
|
return self.convert_to($1, {:at => self.time}.merge(args.first || {}))
|
63
68
|
elsif match && ISO4217.definitions.keys.include?($1.upcase)
|
64
69
|
raise NoRateError.new("Cannot convert to #{$1} because the defined api does not provide a rate")
|
@@ -76,17 +81,18 @@ module Exchange
|
|
76
81
|
# Exchange::Currency.new(40,:usd).convert_to('chf')
|
77
82
|
# @example convert to 'sek' at a specific rate
|
78
83
|
# Exchange::Currency.new(40,:nok).convert_to('sek', :at => Time.gm(2012,2,2))
|
79
|
-
|
84
|
+
#
|
80
85
|
def convert_to other, opts={}
|
81
|
-
Currency.new(
|
86
|
+
Currency.new(Exchange.configuration.api.subclass.new.convert(value, currency, other, opts), other, opts.merge(:from => self))
|
82
87
|
end
|
83
88
|
|
84
89
|
class << self
|
85
90
|
|
86
91
|
private
|
92
|
+
|
87
93
|
# @private
|
88
94
|
# @macro [attach] install_operations
|
89
|
-
|
95
|
+
#
|
90
96
|
def install_operation op
|
91
97
|
define_method op do |*precision|
|
92
98
|
@value = ISO4217.send(op, self.value, self.currency, precision.first)
|
@@ -97,11 +103,11 @@ module Exchange
|
|
97
103
|
# @private
|
98
104
|
# @macro [attach] base_operations
|
99
105
|
# @method $1(other)
|
100
|
-
|
106
|
+
#
|
101
107
|
def base_operation op
|
102
108
|
self.class_eval <<-EOV
|
103
109
|
def #{op}(other)
|
104
|
-
#{'raise CurrencyMixError.new("You\'re trying to mix up #{self.currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !
|
110
|
+
#{'raise CurrencyMixError.new("You\'re trying to mix up #{self.currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Exchange.configuration.allow_mixed_operations && other.kind_of?(Currency) && other.currency != self.currency'}
|
105
111
|
@value #{op}= other.kind_of?(Currency) ? other.convert_to(self.currency, :at => other.time) : other
|
106
112
|
self
|
107
113
|
end
|
@@ -122,7 +128,7 @@ module Exchange
|
|
122
128
|
# @example Round your currency to another number of decimals
|
123
129
|
# Exchange::Currency.new(40.545, :usd).round(0)
|
124
130
|
# #=> #<Exchange::Currency @value=41 @currency=:usd>
|
125
|
-
|
131
|
+
#
|
126
132
|
install_operation :round
|
127
133
|
|
128
134
|
|
@@ -138,7 +144,7 @@ module Exchange
|
|
138
144
|
# @example Ceil your currency to another number of decimals
|
139
145
|
# Exchange::Currency.new(40.445, :usd).ceil(0)
|
140
146
|
# #=> #<Exchange::Currency @value=41 @currency=:usd>
|
141
|
-
|
147
|
+
#
|
142
148
|
install_operation :ceil
|
143
149
|
|
144
150
|
|
@@ -154,7 +160,7 @@ module Exchange
|
|
154
160
|
# @example Floor your currency to another number of decimals
|
155
161
|
# Exchange::Currency.new(40.545, :usd).floor(0)
|
156
162
|
# #=> #<Exchange::Currency @value=40 @currency=:usd>
|
157
|
-
|
163
|
+
#
|
158
164
|
install_operation :floor
|
159
165
|
|
160
166
|
|
@@ -163,13 +169,13 @@ module Exchange
|
|
163
169
|
# @return [Exchange::Currency] The currency with the added value
|
164
170
|
# @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
|
165
171
|
# @example Configuration disallows mixed operations
|
166
|
-
# Exchange
|
172
|
+
# Exchange.configuration.allow_mixed_operations = false
|
167
173
|
# Exchange::Currency.new(20,:nok) + Exchange::Currency.new(20,:sek)
|
168
174
|
# #=> #<CurrencyMixError "You tried to mix currencies">
|
169
175
|
# @example Configuration allows mixed operations (default)
|
170
176
|
# Exchange::Currency.new(20,:nok) + Exchange::Currency.new(20,:sek)
|
171
177
|
# #=> #<Exchange::Currency @value=37.56 @currency=:nok>
|
172
|
-
|
178
|
+
#
|
173
179
|
base_operation '+'
|
174
180
|
|
175
181
|
# Subtract a value from the currency
|
@@ -177,13 +183,13 @@ module Exchange
|
|
177
183
|
# @return [Exchange::Currency] The currency with the added value
|
178
184
|
# @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
|
179
185
|
# @example Configuration disallows mixed operations
|
180
|
-
# Exchange
|
186
|
+
# Exchange.configuration.allow_mixed_operations = false
|
181
187
|
# Exchange::Currency.new(20,:nok) - Exchange::Currency.new(20,:sek)
|
182
188
|
# #=> #<CurrencyMixError "You tried to mix currencies">
|
183
189
|
# @example Configuration allows mixed operations (default)
|
184
190
|
# Exchange::Currency.new(20,:nok) - Exchange::Currency.new(20,:sek)
|
185
191
|
# #=> #<Exchange::Currency @value=7.56 @currency=:nok>
|
186
|
-
|
192
|
+
#
|
187
193
|
base_operation '-'
|
188
194
|
|
189
195
|
# Multiply a value with the currency
|
@@ -191,13 +197,13 @@ module Exchange
|
|
191
197
|
# @return [Exchange::Currency] The currency with the multiplied value
|
192
198
|
# @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
|
193
199
|
# @example Configuration disallows mixed operations
|
194
|
-
# Exchange
|
200
|
+
# Exchange.configuration.allow_mixed_operations = false
|
195
201
|
# Exchange::Currency.new(20,:nok) * Exchange::Currency.new(20,:sek)
|
196
202
|
# #=> #<CurrencyMixError "You tried to mix currencies">
|
197
203
|
# @example Configuration allows mixed operations (default)
|
198
204
|
# Exchange::Currency.new(20,:nok) * Exchange::Currency.new(20,:sek)
|
199
205
|
# #=> #<Exchange::Currency @value=70.56 @currency=:nok>
|
200
|
-
|
206
|
+
#
|
201
207
|
base_operation '*'
|
202
208
|
|
203
209
|
# Divide the currency by a value
|
@@ -205,13 +211,13 @@ module Exchange
|
|
205
211
|
# @return [Exchange::Currency] The currency with the divided value
|
206
212
|
# @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
|
207
213
|
# @example Configuration disallows mixed operations
|
208
|
-
# Exchange
|
214
|
+
# Exchange.configuration.allow_mixed_operations = false
|
209
215
|
# Exchange::Currency.new(20,:nok) / Exchange::Currency.new(20,:sek)
|
210
216
|
# #=> #<CurrencyMixError "You tried to mix currencies">
|
211
217
|
# @example Configuration allows mixed operations (default)
|
212
218
|
# Exchange::Currency.new(20,:nok) / Exchange::Currency.new(20,:sek)
|
213
219
|
# #=> #<Exchange::Currency @value=1.56 @currency=:nok>
|
214
|
-
|
220
|
+
#
|
215
221
|
base_operation '/'
|
216
222
|
|
217
223
|
# Compare a currency with another currency or another value. If the other is not an instance of Exchange::Currency, the value
|
@@ -224,11 +230,11 @@ module Exchange
|
|
224
230
|
# Exchange::Currency.new(40, :usd) == Exchange::Currency.new(34, :eur) #=> true, will implicitly convert eur to usd at the actual rate
|
225
231
|
# @example Compare a currency with a number, the value of the currency will get compared
|
226
232
|
# Exchange::Currency.new(35, :usd) == 35 #=> true
|
227
|
-
|
233
|
+
#
|
228
234
|
def == other
|
229
|
-
if
|
235
|
+
if is_same_currency?(other)
|
230
236
|
other.round.value == self.round.value
|
231
|
-
elsif
|
237
|
+
elsif is_currency?(other)
|
232
238
|
other.convert_to(self.currency, :at => other.time).round.value == self.round.value
|
233
239
|
else
|
234
240
|
self.value == other
|
@@ -241,6 +247,7 @@ module Exchange
|
|
241
247
|
# @return [Fixed] a number which can be used for sorting
|
242
248
|
# @since 0.3
|
243
249
|
# @version 0.3
|
250
|
+
# @todo which historic conversion should be used when two are present?
|
244
251
|
# @example Compare two currencies in terms of value
|
245
252
|
# Exchange::Currency.new(40, :usd) <=> Exchange::Currency.new(28, :usd) #=> -1
|
246
253
|
# @example Compare two different currencies, the other will get converted for comparison
|
@@ -249,17 +256,13 @@ module Exchange
|
|
249
256
|
# [1.usd, 1.eur, 1.chf].sort.map(&:currency) #=> [:usd, :chf, :eur]
|
250
257
|
|
251
258
|
def <=> other
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
1
|
257
|
-
elsif other.is_a?(Exchange::Currency)
|
258
|
-
0
|
259
|
+
if is_same_currency?(other)
|
260
|
+
self.value <=> other.value
|
261
|
+
elsif is_other_currency?(other)
|
262
|
+
self.value <=> other.convert_to(self.currency, :at => other.time).value
|
259
263
|
else
|
260
264
|
self.value <=> other
|
261
265
|
end
|
262
|
-
|
263
266
|
end
|
264
267
|
|
265
268
|
# Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
|
@@ -283,9 +286,37 @@ module Exchange
|
|
283
286
|
format == :amount && ISO4217.stringify(self.value, self.currency, :amount_only => true)
|
284
287
|
].detect{|l| l.is_a?(String) }
|
285
288
|
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
# determine if another given object is an instance of Exchange::Currency
|
293
|
+
# @param [Object] other The object to be tested against
|
294
|
+
# @return [Boolean] true if the other is an instance of Exchange::Currency, false if not
|
295
|
+
#
|
296
|
+
def is_currency? other
|
297
|
+
other.is_a?(Exchange::Currency)
|
298
|
+
end
|
299
|
+
|
300
|
+
# determine if another given object is an instance of Exchange::Currency and the same currency
|
301
|
+
# @param [Object] other The object to be tested against
|
302
|
+
# @return [Boolean] true if the other is an instance of Exchange::Currency and has the same currency as self, false if not
|
303
|
+
#
|
304
|
+
def is_same_currency? other
|
305
|
+
is_currency?(other) && other.currency == self.currency
|
306
|
+
end
|
307
|
+
|
308
|
+
# determine if another given object is an instance of Exchange::Currency and has another currency
|
309
|
+
# @param [Object] other The object to be tested against
|
310
|
+
# @return [Boolean] true if the other is an instance of Exchange::Currency and has another currency as self, false if not
|
311
|
+
#
|
312
|
+
def is_other_currency? other
|
313
|
+
is_currency?(other) && other.currency != self.currency
|
314
|
+
end
|
286
315
|
|
287
316
|
end
|
288
317
|
|
289
318
|
# The error that will get thrown when currencies get mixed up in base operations
|
319
|
+
#
|
290
320
|
CurrencyMixError = Class.new(ArgumentError)
|
321
|
+
|
291
322
|
end
|
@@ -42,27 +42,31 @@ module Exchange
|
|
42
42
|
# end
|
43
43
|
# end
|
44
44
|
# # Now, you can configure your API in the configuration. The Symbol will get camelcased and constantized
|
45
|
-
# Exchange::Configuration.api = :my_custom
|
45
|
+
# Exchange::Configuration.api.subclass = :my_custom
|
46
46
|
# # Have fun, and don't forget to write tests.
|
47
|
-
|
47
|
+
#
|
48
48
|
module ExternalAPI
|
49
49
|
|
50
50
|
# The Base class of all External APIs, handling basic exchange rates and conversion
|
51
51
|
# @author Beat Richartz
|
52
52
|
# @version 0.1
|
53
53
|
# @since 0.1
|
54
|
-
|
54
|
+
#
|
55
55
|
class Base
|
56
|
+
|
56
57
|
# @attr_reader
|
57
58
|
# @return [String] The currency which was the base for the rates
|
59
|
+
#
|
58
60
|
attr_reader :base
|
59
61
|
|
60
62
|
# @attr_reader
|
61
63
|
# @return [Integer] A unix timestamp for the rates, delivered by the API
|
64
|
+
#
|
62
65
|
attr_reader :timestamp
|
63
66
|
|
64
67
|
# @attr_reader
|
65
68
|
# @return [Hash] A Hash which delivers the exchange rate of every available currency to the base currency
|
69
|
+
#
|
66
70
|
attr_reader :rates
|
67
71
|
|
68
72
|
|
@@ -76,8 +80,9 @@ module Exchange
|
|
76
80
|
# @example Get the exchange rate for a conversion from USD to EUR at March 23 2009
|
77
81
|
# Exchange::ExternalAPI::Base.new.rate(:usd, :eur, :at => Time.gm(3,23,2009))
|
78
82
|
# #=> 1.232231231
|
83
|
+
#
|
79
84
|
def rate(from, to, opts={})
|
80
|
-
rate = Exchange
|
85
|
+
rate = Exchange.configuration.cache.subclass.cached(Exchange.configuration.api.subclass, opts.merge(:key_for => [from, to], :plain => true)) do
|
81
86
|
update(opts)
|
82
87
|
rate_from = self.rates[to.to_s.upcase]
|
83
88
|
rate_to = self.rates[from.to_s.upcase]
|
@@ -97,10 +102,11 @@ module Exchange
|
|
97
102
|
# @example Convert 23 EUR to CHF at the rate of December 1 2011
|
98
103
|
# Exchange::ExternalAPI::Base.new.convert(23, :eur, :chf, :at => Time.gm(12,1,2011))
|
99
104
|
# #=> 30.12
|
105
|
+
#
|
100
106
|
def convert(amount, from, to, opts={})
|
101
107
|
amount * rate(from, to, opts)
|
102
108
|
end
|
103
|
-
|
109
|
+
|
104
110
|
end
|
105
111
|
end
|
106
112
|
end
|
@@ -5,7 +5,7 @@ module Exchange
|
|
5
5
|
# @author Beat Richartz
|
6
6
|
# @version 0.1
|
7
7
|
# @since 0.1
|
8
|
-
|
8
|
+
#
|
9
9
|
class Call
|
10
10
|
|
11
11
|
# Initialization of the Call class is the call itself. This means that every instance of the class will only exist during the call
|
@@ -29,13 +29,15 @@ module Exchange
|
|
29
29
|
# @example Call the API and do something with the result
|
30
30
|
# result = Exchange::ExternalAPI::Call.new('http://yourapiurl.com', :format => :xml)
|
31
31
|
# # Do something with that result
|
32
|
-
|
32
|
+
#
|
33
33
|
def initialize url, options={}, &block
|
34
|
-
|
35
|
-
|
34
|
+
Exchange::GemLoader.new('nokogiri').try_load if options[:format] == :xml
|
35
|
+
|
36
|
+
result = Exchange.configuration.cache.subclass.cached(options[:api] || Exchange.configuration.api.subclass, options) do
|
37
|
+
load_url(url, options[:retries] || Exchange.configuration.api.retries, options[:retry_with])
|
36
38
|
end
|
37
39
|
|
38
|
-
parsed = options[:format] == :xml ? Nokogiri.parse(result) : JSON.load(result)
|
40
|
+
parsed = options[:format] == :xml ? Nokogiri.parse(result) : ::JSON.load(result)
|
39
41
|
|
40
42
|
return parsed unless block_given?
|
41
43
|
yield parsed
|
@@ -47,12 +49,11 @@ module Exchange
|
|
47
49
|
# @param [String] url The url to be loaded
|
48
50
|
# @param [Integer] retries The number of retries to do if the API Call should fail with a HTTP Error
|
49
51
|
# @param [Array] retry_with An array of urls to retry the API call with if the call to the original URL should fail. These values will be shifted until a call succeeds or the number of maximum retries is reached
|
50
|
-
|
52
|
+
#
|
51
53
|
def load_url(url, retries, retry_with)
|
52
54
|
begin
|
53
55
|
result = URI.parse(url).open.read
|
54
56
|
rescue SocketError
|
55
|
-
puts 'SocketError'
|
56
57
|
raise APIError.new("Calling API #{url} produced a socket error")
|
57
58
|
rescue OpenURI::HTTPError => e
|
58
59
|
retries -= 1
|
@@ -69,6 +70,8 @@ module Exchange
|
|
69
70
|
end
|
70
71
|
|
71
72
|
# The Api Error to throw when an API Call fails
|
73
|
+
#
|
72
74
|
APIError = Class.new(StandardError)
|
75
|
+
|
73
76
|
end
|
74
77
|
end
|
@@ -1,15 +1,19 @@
|
|
1
1
|
module Exchange
|
2
2
|
module ExternalAPI
|
3
3
|
# The Currency Bot API class, handling communication with the Open Source Currency bot API
|
4
|
-
# You can find further information on the currency bot API here: http://
|
4
|
+
# You can find further information on the currency bot API here: http://openexchangerates.org
|
5
5
|
# @author Beat Richartz
|
6
6
|
# @version 0.1
|
7
7
|
# @since 0.1
|
8
|
-
|
8
|
+
#
|
9
9
|
class CurrencyBot < Base
|
10
|
+
|
10
11
|
# The base of the Currency Bot exchange API
|
12
|
+
#
|
11
13
|
API_URL = 'http://openexchangerates.org/api'
|
14
|
+
|
12
15
|
# The currencies the Currency Bot API can convert
|
16
|
+
#
|
13
17
|
CURRENCIES = %W(xcd usd sar rub nio lak nok omr amd cdf kpw cny kes zwd khr pln mvr gtq clp inr bzd myr hkd sek cop dkk byr lyd ron dzd bif ars gip bob xof std ngn pgk aed mwk cup gmd zwl tzs cve btn xaf ugx syp mad mnt lsl top shp rsd htg mga mzn lvl fkp bwp hnl eur egp chf ils pyg lbp ang kzt wst gyd thb npr kmf irr uyu srd jpy brl szl mop bmd xpf etb jod idr mdl mro yer bam awg nzd pen vef try sll aoa tnd tjs sgd scr lkr mxn ltl huf djf bsd gnf isk vuv sdg gel fjd dop xdr mur php mmk krw lrd bbd zmk zar vnd uah tmt iqd bgn gbp kgs ttd hrk rwf clf bhd uzs twd crc aud mkd pkr afn nad bdt azn czk sos iep pab qar svc sbd all jmd bnd cad kwd ghs)
|
14
18
|
|
15
19
|
# Updates the rates by getting the information from Currency Bot for today or a defined historical date
|
@@ -18,7 +22,7 @@ module Exchange
|
|
18
22
|
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
19
23
|
# @example Update the currency bot API to use the file of March 2, 2010
|
20
24
|
# Exchange::ExternalAPI::CurrencyBot.new.update(:at => Time.gm(3,2,2010))
|
21
|
-
|
25
|
+
#
|
22
26
|
def update(opts={})
|
23
27
|
time = Exchange::Helper.assure_time(opts[:at])
|
24
28
|
|
@@ -36,10 +40,10 @@ module Exchange
|
|
36
40
|
# @return [String] an api url for the time specified
|
37
41
|
# @since 0.1
|
38
42
|
# @version 0.2.6
|
39
|
-
|
43
|
+
#
|
40
44
|
def api_url(time=nil)
|
41
45
|
today = Time.now
|
42
|
-
[API_URL, time && (time.year != today.year || time.yday != today.yday) ? "historical/#{time.strftime("%Y-%m-%d")}.json
|
46
|
+
[API_URL, time && (time.year != today.year || time.yday != today.yday) ? "historical/#{time.strftime("%Y-%m-%d")}.json" : "latest.json"].join('/') + "?app_id=#{Exchange.configuration.api.app_id}"
|
43
47
|
end
|
44
48
|
|
45
49
|
end
|
@@ -6,13 +6,18 @@ module Exchange
|
|
6
6
|
# @author Beat Richartz
|
7
7
|
# @version 0.3
|
8
8
|
# @since 0.3
|
9
|
-
|
10
|
-
class
|
9
|
+
#
|
10
|
+
class Ecb < XML
|
11
|
+
|
11
12
|
# The base of the ECB API URL
|
13
|
+
#
|
12
14
|
API_URL = "http://www.ecb.europa.eu/stats/eurofxref"
|
15
|
+
|
13
16
|
# The currencies the ECB API URL can handle
|
17
|
+
#
|
14
18
|
CURRENCIES = %W(eur usd jpy bgn czk dkk gbp huf ltl lvl pln ron sek chf nok hrk rub try aud brl cad cny hkd idr ils inr krw mxn myr nzd php sgd thb zar)
|
15
19
|
|
20
|
+
# The result of the api call to the Central bank
|
16
21
|
attr_accessor :callresult
|
17
22
|
|
18
23
|
# Updates the rates by getting the information from ECB API for today or a defined historical date
|
@@ -20,23 +25,26 @@ module Exchange
|
|
20
25
|
# any smaller portion history than an epic 4MB XML history file and a 90 day recent history file. We get each of that once and cache it in smaller portions.
|
21
26
|
# @param [Hash] opts Options to define for the API Call
|
22
27
|
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
23
|
-
# @example Update the
|
24
|
-
# Exchange::ExternalAPI::
|
25
|
-
|
28
|
+
# @example Update the ecb API to use the file of March 2, 2010
|
29
|
+
# Exchange::ExternalAPI::Ecb.new.update(:at => Time.gm(3,2,2010))
|
30
|
+
#
|
26
31
|
def update(opts={})
|
27
32
|
time = Exchange::Helper.assure_time(opts[:at], :default => :now)
|
28
|
-
|
29
|
-
times = Exchange::Configuration.retries.times.map{ |i| time - 86400 * (i+1) }
|
30
|
-
|
31
|
-
Kernel.warn "WARNING: Using the ECB API without caching can be very, very slow." unless Exchange::Configuration.cache
|
33
|
+
times = Exchange.configuration.api.retries.times.map{ |i| time - 86400 * (i+1) }
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
+
# Since the Ecb File retrieved can be very large (> 5MB for the history file) and parsing takes a fair amount of time,
|
36
|
+
# caching is doubled on this API
|
37
|
+
#
|
38
|
+
Exchange.configuration.cache.subclass.cached(self.class, :at => time) do
|
39
|
+
Call.new(api_url(time), call_opts(time)) do |result|
|
35
40
|
t = time
|
36
|
-
|
41
|
+
|
42
|
+
# Weekends do not have rates present
|
43
|
+
#
|
37
44
|
while (r = result.css("Cube[time=\"#{t.strftime("%Y-%m-%d")}\"]")).empty? && !times.empty?
|
38
45
|
t = times.shift
|
39
46
|
end
|
47
|
+
|
40
48
|
@callresult = r.to_s
|
41
49
|
end
|
42
50
|
end
|
@@ -55,7 +63,7 @@ module Exchange
|
|
55
63
|
# If it is more than 90 days ago, get the big file
|
56
64
|
# @param [Time] time The exchange rate date for which the URL should be built
|
57
65
|
# @return [String] An ECB API URL to get the xml from
|
58
|
-
|
66
|
+
#
|
59
67
|
def api_url(time)
|
60
68
|
border = Time.now - 90 * 86400
|
61
69
|
[
|
@@ -64,6 +72,14 @@ module Exchange
|
|
64
72
|
].join('/')
|
65
73
|
end
|
66
74
|
|
75
|
+
# a wrapper for the call options, since the cache period is quite complex
|
76
|
+
# @param [Time] time The date of the exchange rate
|
77
|
+
# @return [Hash] a hash with the call options
|
78
|
+
#
|
79
|
+
def call_opts time
|
80
|
+
{:format => :xml, :at => time, :cache => :file, :cache_period => time >= Time.now - 90 * 86400 ? :daily : :monthly}
|
81
|
+
end
|
82
|
+
|
67
83
|
end
|
68
84
|
end
|
69
85
|
end
|