exchange 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|