exchange 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +17 -8
  2. data/Gemfile.lock +12 -6
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +15 -12
  5. data/VERSION +1 -1
  6. data/exchange.gemspec +13 -23
  7. data/lib/core_extensions/conversability.rb +5 -4
  8. data/lib/exchange.rb +6 -11
  9. data/lib/exchange/base.rb +19 -0
  10. data/lib/exchange/cache/base.rb +57 -32
  11. data/lib/exchange/cache/file.rb +41 -45
  12. data/lib/exchange/cache/memcached.rb +28 -29
  13. data/lib/exchange/cache/no_cache.rb +4 -24
  14. data/lib/exchange/cache/rails.rb +26 -26
  15. data/lib/exchange/cache/redis.rb +30 -33
  16. data/lib/exchange/configuration.rb +167 -73
  17. data/lib/exchange/currency.rb +61 -30
  18. data/lib/exchange/external_api.rb +2 -0
  19. data/lib/exchange/external_api/base.rb +11 -5
  20. data/lib/exchange/external_api/call.rb +10 -7
  21. data/lib/exchange/external_api/currency_bot.rb +9 -5
  22. data/lib/exchange/external_api/ecb.rb +29 -13
  23. data/lib/exchange/external_api/json.rb +22 -0
  24. data/lib/exchange/external_api/xavier_media.rb +6 -5
  25. data/lib/exchange/external_api/xml.rb +22 -0
  26. data/lib/exchange/gem_loader.rb +35 -0
  27. data/lib/exchange/helper.rb +22 -15
  28. data/lib/exchange/iso_4217.rb +55 -44
  29. data/spec/core_extensions/conversability_spec.rb +2 -2
  30. data/spec/exchange/cache/base_spec.rb +7 -7
  31. data/spec/exchange/cache/file_spec.rb +19 -18
  32. data/spec/exchange/cache/memcached_spec.rb +29 -26
  33. data/spec/exchange/cache/no_cache_spec.rb +12 -6
  34. data/spec/exchange/cache/rails_spec.rb +13 -9
  35. data/spec/exchange/cache/redis_spec.rb +24 -24
  36. data/spec/exchange/configuration_spec.rb +38 -30
  37. data/spec/exchange/currency_spec.rb +33 -21
  38. data/spec/exchange/external_api/base_spec.rb +3 -1
  39. data/spec/exchange/external_api/call_spec.rb +5 -1
  40. data/spec/exchange/external_api/currency_bot_spec.rb +5 -1
  41. data/spec/exchange/external_api/ecb_spec.rb +9 -5
  42. data/spec/exchange/external_api/xavier_media_spec.rb +5 -1
  43. data/spec/exchange/gem_loader_spec.rb +29 -0
  44. data/spec/spec_helper.rb +1 -1
  45. metadata +12 -87
@@ -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 && Configuration.api_class::CURRENCIES.include?($1)
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(Configuration.api_class.new.convert(value, currency, other, opts), other, opts.merge(:from => self))
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 !Configuration.allow_mixed_operations && other.kind_of?(Currency) && other.currency != self.currency'}
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::Configuration.allow_mixed_operations = false
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::Configuration.allow_mixed_operations = false
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::Configuration.allow_mixed_operations = false
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::Configuration.allow_mixed_operations = false
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 other.is_a?(Exchange::Currency) && other.currency == self.currency
235
+ if is_same_currency?(other)
230
236
  other.round.value == self.round.value
231
- elsif other.is_a?(Exchange::Currency)
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
- # TODO which historic conversion should be used when two are present?
253
- if other.is_a?(Exchange::Currency) && ((other.currency == self.currency && self.value < other.value) || (other.currency != self.currency && self.value < other.convert_to(self.currency, :at => other.time).value))
254
- -1
255
- elsif other.is_a?(Exchange::Currency) && ((other.currency == self.currency && self.value > other.value) || (other.currency != self.currency && self.value > other.convert_to(self.currency, :at => other.time).value))
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
@@ -1,4 +1,6 @@
1
1
  require 'exchange/external_api/base'
2
+ require 'exchange/external_api/xml'
3
+ require 'exchange/external_api/json'
2
4
  require 'exchange/external_api/call'
3
5
  require 'exchange/external_api/currency_bot'
4
6
  require 'exchange/external_api/xavier_media'
@@ -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::Configuration.cache_class.cached(Exchange::Configuration.api, opts.merge(:key_for => [from, to], :plain => true)) do
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
- result = Exchange::Configuration.cache_class(options).cached(options[:api] || Exchange::Configuration.api, options) do
35
- load_url(url, options[:retries] || Exchange::Configuration.retries, options[:retry_with])
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://currencybot.github.com/
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?app_id=#{Exchange::Configuration.api_app_id}" : "latest.json?app_id=#{Exchange::Configuration.api_app_id}"].join('/')
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 ECB < Base
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 currency bot API to use the file of March 2, 2010
24
- # Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
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
- api_url = api_url(time)
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
- Exchange::Configuration.cache_class.cached(self.class, :at => time) do
34
- Call.new(api_url, :format => :xml, :at => time, :cache => :file, :cache_period => time >= Time.now - 90 * 86400 ? :daily : :monthly) do |result|
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