exchange 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -1
  3. data/Gemfile.lock +3 -3
  4. data/README.rdoc +115 -47
  5. data/benchmark/benchmark.rb +49 -0
  6. data/changelog.rdoc +8 -1
  7. data/lib/exchange.rb +4 -4
  8. data/lib/exchange/base.rb +1 -1
  9. data/lib/exchange/cache.rb +2 -0
  10. data/lib/exchange/cache/base.rb +20 -6
  11. data/lib/exchange/cache/configuration.rb +24 -0
  12. data/lib/exchange/cache/file.rb +24 -9
  13. data/lib/exchange/cache/memcached.rb +3 -3
  14. data/lib/exchange/cache/memory.rb +89 -0
  15. data/lib/exchange/cache/rails.rb +1 -1
  16. data/lib/exchange/cache/redis.rb +4 -4
  17. data/lib/exchange/configurable.rb +53 -0
  18. data/lib/exchange/configuration.rb +32 -26
  19. data/lib/exchange/core_extensions.rb +3 -0
  20. data/lib/exchange/core_extensions/cachify.rb +25 -0
  21. data/lib/exchange/core_extensions/float/error_safe.rb +25 -0
  22. data/lib/{core_extensions → exchange/core_extensions/numeric}/conversability.rb +12 -12
  23. data/lib/exchange/external_api.rb +2 -1
  24. data/lib/exchange/external_api/base.rb +34 -9
  25. data/lib/exchange/external_api/call.rb +6 -8
  26. data/lib/exchange/external_api/configuration.rb +25 -0
  27. data/lib/exchange/external_api/ecb.rb +16 -25
  28. data/lib/exchange/external_api/json.rb +11 -1
  29. data/lib/exchange/external_api/open_exchange_rates.rb +65 -0
  30. data/lib/exchange/external_api/xavier_media.rb +7 -7
  31. data/lib/exchange/iso_4217.rb +32 -5
  32. data/lib/exchange/{currency.rb → money.rb} +112 -110
  33. data/lib/exchange/typecasting.rb +94 -0
  34. data/spec/exchange/cache/base_spec.rb +2 -2
  35. data/spec/exchange/cache/configuration_spec.rb +56 -0
  36. data/spec/exchange/cache/file_spec.rb +10 -8
  37. data/spec/exchange/cache/memcached_spec.rb +9 -18
  38. data/spec/exchange/cache/memory_spec.rb +122 -0
  39. data/spec/exchange/cache/no_cache_spec.rb +5 -15
  40. data/spec/exchange/cache/rails_spec.rb +2 -6
  41. data/spec/exchange/cache/redis_spec.rb +8 -18
  42. data/spec/exchange/configuration_spec.rb +31 -7
  43. data/spec/exchange/core_extensions/array/cachify_spec.rb +12 -0
  44. data/spec/exchange/core_extensions/float/error_safe_spec.rb +49 -0
  45. data/spec/exchange/core_extensions/hash/cachify_spec.rb +12 -0
  46. data/spec/exchange/core_extensions/numeric/cachify_spec.rb +26 -0
  47. data/spec/{core_extensions → exchange/core_extensions/numeric}/conversability_spec.rb +22 -22
  48. data/spec/exchange/core_extensions/string/cachify_spec.rb +59 -0
  49. data/spec/exchange/core_extensions/symbol/cachify_spec.rb +12 -0
  50. data/spec/exchange/external_api/base_spec.rb +10 -7
  51. data/spec/exchange/external_api/call_spec.rb +3 -0
  52. data/spec/exchange/external_api/configuration_spec.rb +52 -0
  53. data/spec/exchange/external_api/ecb_spec.rb +8 -5
  54. data/spec/exchange/external_api/open_exchange_rates_spec.rb +70 -0
  55. data/spec/exchange/external_api/xavier_media_spec.rb +8 -5
  56. data/spec/exchange/iso_4217_spec.rb +208 -20
  57. data/spec/exchange/{currency_spec.rb → money_spec.rb} +102 -82
  58. data/spec/exchange/typecasting_spec.rb +86 -0
  59. metadata +117 -71
  60. data/exchange-0.7.5.gem +0 -0
  61. data/exchange-0.7.6.gem +0 -0
  62. data/lib/exchange/external_api/currency_bot.rb +0 -61
  63. data/spec/exchange/external_api/currency_bot_spec.rb +0 -63
@@ -12,24 +12,24 @@ module Exchange
12
12
  # and others via method missing, this is not handled via method missing because it would seriously break down performance.
13
13
  #
14
14
  # @example Instantiate from any type of number
15
- # 40.usd => #<Exchange::Currency @value=40 @currency=:usd>
16
- # -33.nok => #<Exchange::Currency @value=-33 @currency=:nok>
17
- # 33.333.sek => #<Exchange::Currency @value=33.333 @currency=:sek>
15
+ # 40.usd => #<Exchange::Money @value=40 @currency=:usd>
16
+ # -33.nok => #<Exchange::Money @value=-33 @currency=:nok>
17
+ # 33.333.sek => #<Exchange::Money @value=33.333 @currency=:sek>
18
18
  # @example Instantiate and immediatly convert
19
- # 1.usd.to_eur => #<Exchange::Currency @value=0.79 @currency=:eur>
20
- # 1.nok.to_chf => #<Exchange::Currency @value=6.55 @currency=:chf>
21
- # -3.5.dkk.to_huf => #<Exchange::Currency @value=-346.55 @currency=:huf>
19
+ # 1.usd.to_eur => #<Exchange::Money @value=0.79 @currency=:eur>
20
+ # 1.nok.to_chf => #<Exchange::Money @value=6.55 @currency=:chf>
21
+ # -3.5.dkk.to_huf => #<Exchange::Money @value=-346.55 @currency=:huf>
22
22
  # @example Instantiate and immediatly convert at a specific time in the past
23
- # 1.usd.to_eur(:at => Time.now - 86400) => #<Exchange::Currency @value=0.80 @currency=:eur>
24
- # 1.nok.to_chf(:at => Time.now - 3600) => #<Exchange::Currency @value=6.57 @currency=:chf>
25
- # -3.5.dkk.to_huf(:at => Time.now - 172800) => #<Exchange::Currency @value=-337.40 @currency=:huf>
23
+ # 1.usd.to_eur(:at => Time.now - 86400) => #<Exchange::Money @value=0.80 @currency=:eur>
24
+ # 1.nok.to_chf(:at => Time.now - 3600) => #<Exchange::Money @value=6.57 @currency=:chf>
25
+ # -3.5.dkk.to_huf(:at => Time.now - 172800) => #<Exchange::Money @value=-337.40 @currency=:huf>
26
26
  #
27
27
  # @since 0.1
28
28
  # @version 0.2
29
29
  #
30
- ISO4217.definitions.keys.each do |c|
31
- define_method c.downcase.to_sym do |*args|
32
- Currency.new(self, c, *args)
30
+ ISO4217.currencies.each do |c|
31
+ define_method c do |*args|
32
+ Money.new(self, c, *args)
33
33
  end
34
34
  end
35
35
 
@@ -1,7 +1,8 @@
1
+ require 'exchange/external_api/configuration'
1
2
  require 'exchange/external_api/base'
2
3
  require 'exchange/external_api/xml'
3
4
  require 'exchange/external_api/json'
4
5
  require 'exchange/external_api/call'
5
- require 'exchange/external_api/currency_bot'
6
+ require 'exchange/external_api/open_exchange_rates'
6
7
  require 'exchange/external_api/xavier_media'
7
8
  require 'exchange/external_api/ecb'
@@ -9,14 +9,21 @@ module Exchange
9
9
  # @example Easily connect to your custom API by writing an ExternalAPI Class
10
10
  # module Exchange
11
11
  # module ExternalAPI
12
- # class MyCustom < Base
12
+ #
13
+ # # Inherit from Json to write for a json api, and the json gem is automatically loaded
14
+ # # Inherit from XML to write for an xml api, and nokogiri is automatically loaded
15
+ # #
16
+ # class MyCustom < Json
17
+ #
13
18
  # # Define here which currencies your API can handle
14
- # CURRENCIES = %W(usd chf)
19
+ # #
20
+ # CURRENCIES = %W(usd chf).map(&:to_sym)
15
21
  #
16
22
  # # Every instance of ExternalAPI Class has to have an update function which
17
23
  # # gets the rates from the API
18
24
  # #
19
25
  # def update(opts={})
26
+ #
20
27
  # # assure that you will get a Time object for the historical dates
21
28
  # #
22
29
  # time = helper.assure_time(opts[:at])
@@ -44,6 +51,7 @@ module Exchange
44
51
  # # Attention, this is readonly, self.timestamp= won't work
45
52
  # #
46
53
  # @timestamp = result['timestamp'].to_i
54
+ #
47
55
  # end
48
56
  #
49
57
  # private
@@ -120,19 +128,19 @@ module Exchange
120
128
  # Exchange::ExternalAPI::Base.new.rate(:usd, :eur, :at => Time.gm(3,23,2009))
121
129
  # #=> 1.232231231
122
130
  #
123
- def rate(from, to, opts={})
124
- rate = cache.cached(api, opts.merge(:key_for => [from, to], :plain => true)) do
131
+ def rate(from, to, opts={})
132
+ rate = cache.cached(api, opts.merge(:key_for => [from, to])) do
125
133
  update(opts)
126
134
 
127
- rate_from = self.rates[to.to_s.upcase]
128
- rate_to = self.rates[from.to_s.upcase]
135
+ rate_from = rates[from]
136
+ rate_to = rates[to]
129
137
 
130
138
  test_for_rates_and_raise_if_nil rate_from, rate_to, opts[:at]
131
139
 
132
- rate_from / rate_to
140
+ rate_to / rate_from
133
141
  end
134
142
 
135
- BigDecimal.new(rate.to_s)
143
+ rate.is_a?(BigDecimal) ? rate : BigDecimal.new(rate.to_s)
136
144
  end
137
145
 
138
146
  # Converts an amount of one currency into another
@@ -165,8 +173,25 @@ module Exchange
165
173
  # @raise [NoRateError] An error indicating that there is no rate present when there is no rate present
166
174
  #
167
175
  def test_for_rates_and_raise_if_nil rate_from, rate_to, time=nil
168
- raise NoRateError.new("No rates where found for #{from} to #{to} #{'at ' + time.to_s if time}") unless rate_from && rate_to
176
+ raise NoRateError.new("No rates where found for #{rate_from} to #{rate_to} #{'at ' + time.to_s if time}") unless rate_from && rate_to
169
177
  end
178
+
179
+ protected
180
+
181
+ # Convenience accessor to api configuration
182
+ # @return [Exchange::ExternalAPI::Configuration] the current configuration
183
+ #
184
+ def config
185
+ @config ||= Exchange.configuration.api
186
+ end
187
+
188
+ # Convenience accessor to the cache configuration
189
+ # @return [Exchange::Cache::Configuration] the current configuration
190
+ #
191
+ def cache_config
192
+ @cache_config ||= Exchange.configuration.cache
193
+ end
194
+
170
195
  end
171
196
  end
172
197
  end
@@ -6,7 +6,7 @@ module Exchange
6
6
  # @version 0.1
7
7
  # @since 0.1
8
8
  #
9
- class Call
9
+ class Call < Base
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
12
12
  # @param [String] url The url of the API to call
@@ -31,12 +31,10 @@ module Exchange
31
31
  # # Do something with that result
32
32
  #
33
33
  def initialize url, options={}, &block
34
- Exchange::GemLoader.new('nokogiri').try_load if options[:format] == :xml
35
-
36
- api_config = Exchange.configuration.api
37
-
38
- result = Exchange.configuration.cache.subclass.cached(options[:api] || api_config.subclass, options) do
39
- load_url(url, options[:retries] || api_config.retries, options[:retry_with])
34
+ Exchange::GemLoader.new(options[:format] == :xml ? 'nokogiri' : 'json').try_load
35
+
36
+ result = cache_config.subclass.cached(options[:api] || config.subclass, options) do
37
+ load_url(url, options[:retries] || config.retries, options[:retry_with])
40
38
  end
41
39
 
42
40
  parsed = options[:format] == :xml ? Nokogiri::XML.parse(result.sub("\n", '')) : ::JSON.load(result)
@@ -64,7 +62,7 @@ module Exchange
64
62
  url = retry_with.shift if retry_with && !retry_with.empty?
65
63
  retry
66
64
  else
67
- raise APIError.new("API #{url} was not reachable")
65
+ raise APIError.new("API #{url} was not reachable and returned #{e.message}. May be you requested a historic rate not provided")
68
66
  end
69
67
  end
70
68
  result
@@ -0,0 +1,25 @@
1
+ module Exchange
2
+ module ExternalAPI
3
+ # @author Beat Richartz
4
+ # A Class that handles api configuration options
5
+ #
6
+ # @version 0.9
7
+ # @since 0.9
8
+ #
9
+ class Configuration < Exchange::Configurable
10
+
11
+ attr_accessor :retries, :app_id, :protocol
12
+
13
+ def_delegators :instance, :retries, :retries=, :app_id, :app_id=, :protocol, :protocol=
14
+
15
+ def parent_module
16
+ ExternalAPI
17
+ end
18
+
19
+ def key
20
+ :api
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -11,11 +11,11 @@ module Exchange
11
11
 
12
12
  # The base of the ECB API URL
13
13
  #
14
- API_URL = "http://www.ecb.europa.eu/stats/eurofxref"
14
+ API_URL = "www.ecb.europa.eu/stats/eurofxref"
15
15
 
16
16
  # The currencies the ECB API URL can handle
17
17
  #
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)
18
+ CURRENCIES = [: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]
19
19
 
20
20
  # The result of the api call to the Central bank
21
21
  attr_accessor :callresult
@@ -35,26 +35,17 @@ module Exchange
35
35
  time = helper.assure_time(opts[:at], :default => :now)
36
36
  times = map_retry_times time
37
37
 
38
- # Since the Ecb File retrieved can be very large (> 5MB for the history file) and parsing takes a fair amount of time,
39
- # caching is doubled on this API
40
- #
41
- cache.cached(self.class, :at => time) do
42
- Call.new(api_url(time), call_opts(time)) do |result|
43
- t = time
44
-
45
- # Weekends do not have rates present
46
- #
47
- t = times.shift while (r = find_rate!(result, t)).empty? && !times.empty?
48
-
49
- @callresult = r.to_s
50
- end
38
+ Call.new(api_url(time), call_opts(time)) do |result|
39
+ t = time
40
+
41
+ # Weekends do not have rates present
42
+ #
43
+ t = times.shift while (r = find_rate!(result, t)).empty? && !times.empty?
44
+
45
+ @base = :eur # We just have to assume, since it's the ECB
46
+ @rates = extract_rates(r.children)
47
+ @timestamp = time.to_i
51
48
  end
52
-
53
- parsed = Nokogiri.parse(self.callresult)
54
-
55
- @base = 'EUR' # We just have to assume, since it's the ECB
56
- @rates = extract_rates(parsed.children.children)
57
- @timestamp = time.to_i
58
49
  end
59
50
 
60
51
  private
@@ -67,7 +58,7 @@ module Exchange
67
58
  #
68
59
  def api_url(time)
69
60
  border = Time.now - 90 * 86400
70
- [
61
+ [ "#{config.protocol}:/",
71
62
  API_URL,
72
63
  border <= time ? 'eurofxref-hist-90d.xml' : 'eurofxref-hist.xml'
73
64
  ].join('/')
@@ -96,7 +87,7 @@ module Exchange
96
87
  map_to_currency_or_rate c
97
88
  }.compact.flatten
98
89
 
99
- to_hash!(['EUR', BigDecimal.new("1")] + rate_array)
90
+ to_hash!([:eur, BigDecimal.new("1")] + rate_array)
100
91
  end
101
92
 
102
93
  # a helper method to map a key value pair to either currency or rate
@@ -109,7 +100,7 @@ module Exchange
109
100
  unless (values = xml.attributes.values).empty?
110
101
  values.map { |v|
111
102
  val = v.value
112
- val.match(/\d+/) ? BigDecimal.new(val) : val
103
+ val.match(/\d+/) ? BigDecimal.new(val) : val.downcase.to_sym
113
104
  }.sort_by(&:to_s).reverse
114
105
  end
115
106
  end
@@ -121,7 +112,7 @@ module Exchange
121
112
  # @version 0.7
122
113
  #
123
114
  def map_retry_times time
124
- Exchange.configuration.api.retries.times.map{ |i| time - 86400 * (i+1) }
115
+ config.retries.times.map{ |i| time - 86400 * (i+1) }
125
116
  end
126
117
 
127
118
  # a wrapper for the call options, since the cache period is quite complex
@@ -7,6 +7,16 @@ module Exchange
7
7
  # @version 0.6
8
8
  # @since 0.6
9
9
  #
10
- JSON = Class.new Base
10
+ class Json < Base
11
+
12
+ # Initializer, essentially takes the arguments passed to initialization, loads json on the way
13
+ # and passes the arguments to the api base
14
+ #
15
+ def initialize *args
16
+ Exchange::GemLoader.new('json').try_load unless defined?(JSON)
17
+ super *args
18
+ end
19
+
20
+ end
11
21
  end
12
22
  end
@@ -0,0 +1,65 @@
1
+ module Exchange
2
+ module ExternalAPI
3
+ # The Open Exchange Rates API class, handling communication with the Open Source Currency bot API
4
+ # You can find further information on the Open Exchange Rates API here: http://openexchangerates.org
5
+ # @author Beat Richartz
6
+ # @version 0.1
7
+ # @since 0.1
8
+ #
9
+ class OpenExchangeRates < Base
10
+
11
+ # The base of the Open Exchange Rates exchange API
12
+ #
13
+ API_URL = 'openexchangerates.org/api'
14
+
15
+ # The currencies the Open Exchange Rates API can convert
16
+ #
17
+ CURRENCIES = [: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]
18
+
19
+ # Updates the rates by getting the information from Open Exchange Rates for today or a defined historical date
20
+ # The call gets cached for a maximum of 24 hours.
21
+ # @param [Hash] opts Options to define for the API Call
22
+ # @option opts [Time, String] :at a historical date to get the exchange rates for
23
+ # @example Update the Open Exchange Rates API to use the file of March 2, 2010
24
+ # Exchange::ExternalAPI::OpenExchangeRates.new.update(:at => Time.gm(3,2,2010))
25
+ #
26
+ def update(opts={})
27
+ time = helper.assure_time(opts[:at])
28
+
29
+ Call.new(api_url(time), :at => time) do |result|
30
+ @base = result['base'].downcase.to_sym
31
+ @rates = extract_rates(result)
32
+ @timestamp = result['timestamp'].to_i
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Helper method to extract rates from the api call result
39
+ # @param [JSON] parsed The parsed result
40
+ # @return [Hash] A hash with rates
41
+ # @since 0.7
42
+ # @version 0.7
43
+ #
44
+ def extract_rates parsed
45
+ to_hash! parsed['rates'].keys.map{|k| k.downcase.to_sym }.zip(parsed['rates'].values.map{|v| BigDecimal.new(v.to_s) }).flatten
46
+ end
47
+
48
+ # A helper function to build an api url for either a specific time or the latest available rates
49
+ # @param [Time] time The time to build the api url for
50
+ # @return [String] an api url for the time specified
51
+ # @since 0.1
52
+ # @version 0.2.6
53
+ #
54
+ def api_url(time=nil)
55
+ today = Time.now
56
+ [
57
+ "#{config.protocol}:/",
58
+ API_URL,
59
+ time && (time.year != today.year || time.yday != today.yday) ? "historical/#{time.strftime("%Y-%m-%d")}.json" : "latest.json"
60
+ ].join('/') + "?app_id=#{config.app_id}"
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -10,9 +10,9 @@ module Exchange
10
10
  class XavierMedia < XML
11
11
 
12
12
  # The base of the Xaviermedia API URL
13
- API_URL = "http://api.finance.xaviermedia.com/api"
13
+ API_URL = "api.finance.xaviermedia.com/api"
14
14
  # The currencies the Xaviermedia API URL can handle
15
- CURRENCIES = %W(eur usd jpy gbp cyp czk dkk eek huf ltl mtl pln sek sit skk chf isk nok bgn hrk rol ron rub trl aud cad cny hkd idr krw myr nzd php sgd thb zar)
15
+ CURRENCIES = [:eur, :usd, :jpy, :gbp, :cyp, :czk, :dkk, :eek, :huf, :ltl, :mtl, :pln, :sek, :sit, :skk, :chf, :isk, :nok, :bgn, :hrk, :rol, :ron, :rub, :trl, :aud, :cad, :cny, :hkd, :idr, :krw, :myr, :nzd, :php, :sgd, :thb, :zar]
16
16
 
17
17
  # Updates the rates by getting the information from Xaviermedia API for today or a defined historical date
18
18
  # The call gets cached for a maximum of 24 hours.
@@ -40,7 +40,7 @@ module Exchange
40
40
  # @return [String] An Xaviermedia API URL for the specified time
41
41
  #
42
42
  def api_url(time)
43
- [API_URL, "#{time.strftime("%Y/%m/%d")}.xml"].join('/')
43
+ ["#{config.protocol}:/", API_URL, "#{time.strftime("%Y/%m/%d")}.xml"].join('/')
44
44
  end
45
45
 
46
46
  # Options for the API call to make
@@ -51,7 +51,7 @@ module Exchange
51
51
  # @version 0.6
52
52
  #
53
53
  def api_opts(opts={})
54
- retry_urls = Exchange.configuration.api.retries.times.map { |i| api_url(opts[:at] - 86400 * (i+1)) }
54
+ retry_urls = config.retries.times.map { |i| api_url(opts[:at] - 86400 * (i+1)) }
55
55
 
56
56
  { :format => :xml, :at => opts[:at], :retry_with => retry_urls }
57
57
  end
@@ -73,18 +73,18 @@ module Exchange
73
73
  # @version 0.7
74
74
  #
75
75
  def extract_rates(result)
76
- rates_array = result.css('fx currency_code').children.map(&:to_s).zip(result.css('fx rate').children.map{|c| BigDecimal.new(c.to_s) }).flatten
76
+ rates_array = result.css('fx currency_code').children.map{|c| c.to_s.downcase.to_sym }.zip(result.css('fx rate').children.map{|c| BigDecimal.new(c.to_s) }).flatten
77
77
  to_hash!(rates_array)
78
78
  end
79
79
 
80
80
  # Extract the base currency from the callresult
81
81
  # @param [Nokogiri::XML] result the callresult
82
- # @return [String] The base currency for the rates
82
+ # @return [Symbol] The base currency for the rates
83
83
  # @since 0.7
84
84
  # @version 0.7
85
85
  #
86
86
  def extract_base_currency(result)
87
- result.css('basecurrency').children[0].to_s
87
+ result.css('basecurrency').children[0].to_s.downcase.to_sym
88
88
  end
89
89
 
90
90
  end
@@ -23,7 +23,7 @@ module Exchange
23
23
  def install_operation op
24
24
  self.class_eval <<-EOV
25
25
  def #{op}(amount, currency, precision=nil)
26
- minor = definitions[currency.to_s.upcase]['minor_unit']
26
+ minor = definitions[currency][:minor_unit]
27
27
  (amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, minor)).#{op}(precision || minor)
28
28
  end
29
29
  EOV
@@ -36,7 +36,34 @@ module Exchange
36
36
  # @return [Hash] The iso427 Definitions with the currency code as keys
37
37
  #
38
38
  def definitions
39
- @@definitions ||= YAML.load_file(File.join(ROOT_PATH, 'iso4217.yml'))
39
+ return @definitions if @definitions
40
+ loaded = YAML.load_file(File.join(ROOT_PATH, 'iso4217.yml'))
41
+ @definitions = {}
42
+
43
+ loaded.each_pair do |k,v|
44
+ v.keys.each do |key|
45
+ v[key.to_sym] = v.delete(key)
46
+ end
47
+
48
+ @definitions[k.downcase.to_sym] = v
49
+ end
50
+
51
+ @definitions
52
+ end
53
+
54
+ # All currencies defined by ISO 4217 as an array of symbols for inclusion testing
55
+ # @return [Array] An Array of currency symbols
56
+ #
57
+ def currencies
58
+ @currencies ||= definitions.keys.map(&:to_s).sort.map(&:to_sym)
59
+ end
60
+
61
+ # Check if a currency is defined by ISO 4217 standards
62
+ # @param [Symbol] currency the downcased currency symbol
63
+ # @return [Boolean] true if the symbol matches a currency, false if not
64
+ #
65
+ def defines? currency
66
+ currencies.include? currency
40
67
  end
41
68
 
42
69
  # Use this to instantiate a currency amount. For one, it is important that we use BigDecimal here so nothing gets lost because
@@ -48,7 +75,7 @@ module Exchange
48
75
  # Exchange::ISO4217.instantiate("4523", "usd") #=> #<Bigdecimal 4523.00>
49
76
  #
50
77
  def instantiate(amount, currency)
51
- BigDecimal.new(amount.to_s, definitions[currency.to_s.upcase]['minor_unit'])
78
+ BigDecimal.new(amount.to_s, definitions[currency][:minor_unit])
52
79
  end
53
80
 
54
81
  # Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
@@ -68,7 +95,7 @@ module Exchange
68
95
  # Exchange::ISO4217.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
69
96
  #
70
97
  def stringify(amount, currency, opts={})
71
- format = "%.#{definitions[currency.to_s.upcase]['minor_unit']}f"
98
+ format = "%.#{definitions[currency][:minor_unit]}f"
72
99
  "#{currency.to_s.upcase + ' ' unless opts[:amount_only]}#{format % amount}"
73
100
  end
74
101
 
@@ -101,7 +128,7 @@ module Exchange
101
128
 
102
129
  # Forwards the assure_time method to the instance using singleforwardable
103
130
  #
104
- def_delegators :instance, :definitions, :instantiate, :stringify, :round, :ceil, :floor
131
+ def_delegators :instance, :definitions, :instantiate, :stringify, :round, :ceil, :floor, :currencies, :defines?
105
132
 
106
133
  end
107
134
  end