exchange 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +1 -1
- data/Gemfile.lock +3 -3
- data/README.rdoc +115 -47
- data/benchmark/benchmark.rb +49 -0
- data/changelog.rdoc +8 -1
- data/lib/exchange.rb +4 -4
- data/lib/exchange/base.rb +1 -1
- data/lib/exchange/cache.rb +2 -0
- data/lib/exchange/cache/base.rb +20 -6
- data/lib/exchange/cache/configuration.rb +24 -0
- data/lib/exchange/cache/file.rb +24 -9
- data/lib/exchange/cache/memcached.rb +3 -3
- data/lib/exchange/cache/memory.rb +89 -0
- data/lib/exchange/cache/rails.rb +1 -1
- data/lib/exchange/cache/redis.rb +4 -4
- data/lib/exchange/configurable.rb +53 -0
- data/lib/exchange/configuration.rb +32 -26
- data/lib/exchange/core_extensions.rb +3 -0
- data/lib/exchange/core_extensions/cachify.rb +25 -0
- data/lib/exchange/core_extensions/float/error_safe.rb +25 -0
- data/lib/{core_extensions → exchange/core_extensions/numeric}/conversability.rb +12 -12
- data/lib/exchange/external_api.rb +2 -1
- data/lib/exchange/external_api/base.rb +34 -9
- data/lib/exchange/external_api/call.rb +6 -8
- data/lib/exchange/external_api/configuration.rb +25 -0
- data/lib/exchange/external_api/ecb.rb +16 -25
- data/lib/exchange/external_api/json.rb +11 -1
- data/lib/exchange/external_api/open_exchange_rates.rb +65 -0
- data/lib/exchange/external_api/xavier_media.rb +7 -7
- data/lib/exchange/iso_4217.rb +32 -5
- data/lib/exchange/{currency.rb → money.rb} +112 -110
- data/lib/exchange/typecasting.rb +94 -0
- data/spec/exchange/cache/base_spec.rb +2 -2
- data/spec/exchange/cache/configuration_spec.rb +56 -0
- data/spec/exchange/cache/file_spec.rb +10 -8
- data/spec/exchange/cache/memcached_spec.rb +9 -18
- data/spec/exchange/cache/memory_spec.rb +122 -0
- data/spec/exchange/cache/no_cache_spec.rb +5 -15
- data/spec/exchange/cache/rails_spec.rb +2 -6
- data/spec/exchange/cache/redis_spec.rb +8 -18
- data/spec/exchange/configuration_spec.rb +31 -7
- data/spec/exchange/core_extensions/array/cachify_spec.rb +12 -0
- data/spec/exchange/core_extensions/float/error_safe_spec.rb +49 -0
- data/spec/exchange/core_extensions/hash/cachify_spec.rb +12 -0
- data/spec/exchange/core_extensions/numeric/cachify_spec.rb +26 -0
- data/spec/{core_extensions → exchange/core_extensions/numeric}/conversability_spec.rb +22 -22
- data/spec/exchange/core_extensions/string/cachify_spec.rb +59 -0
- data/spec/exchange/core_extensions/symbol/cachify_spec.rb +12 -0
- data/spec/exchange/external_api/base_spec.rb +10 -7
- data/spec/exchange/external_api/call_spec.rb +3 -0
- data/spec/exchange/external_api/configuration_spec.rb +52 -0
- data/spec/exchange/external_api/ecb_spec.rb +8 -5
- data/spec/exchange/external_api/open_exchange_rates_spec.rb +70 -0
- data/spec/exchange/external_api/xavier_media_spec.rb +8 -5
- data/spec/exchange/iso_4217_spec.rb +208 -20
- data/spec/exchange/{currency_spec.rb → money_spec.rb} +102 -82
- data/spec/exchange/typecasting_spec.rb +86 -0
- metadata +117 -71
- data/exchange-0.7.5.gem +0 -0
- data/exchange-0.7.6.gem +0 -0
- data/lib/exchange/external_api/currency_bot.rb +0 -61
- 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::
|
16
|
-
# -33.nok => #<Exchange::
|
17
|
-
# 33.333.sek => #<Exchange::
|
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::
|
20
|
-
# 1.nok.to_chf => #<Exchange::
|
21
|
-
# -3.5.dkk.to_huf => #<Exchange::
|
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::
|
24
|
-
# 1.nok.to_chf(:at => Time.now - 3600) => #<Exchange::
|
25
|
-
# -3.5.dkk.to_huf(:at => Time.now - 172800) => #<Exchange::
|
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.
|
31
|
-
define_method c
|
32
|
-
|
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/
|
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
|
-
#
|
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
|
-
#
|
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]
|
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 =
|
128
|
-
rate_to =
|
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
|
-
|
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 #{
|
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(
|
35
|
-
|
36
|
-
|
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 = "
|
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 =
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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!([
|
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
|
-
|
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
|
-
|
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 = "
|
13
|
+
API_URL = "api.finance.xaviermedia.com/api"
|
14
14
|
# The currencies the Xaviermedia API URL can handle
|
15
|
-
CURRENCIES =
|
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 =
|
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
|
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 [
|
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
|
data/lib/exchange/iso_4217.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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
|
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
|