exchange 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/README.rdoc +5 -3
- data/VERSION +1 -1
- data/exchange.gemspec +16 -2
- data/iso4217.yml +589 -0
- data/lib/core_extensions/conversability.rb +12 -7
- data/lib/exchange.rb +7 -1
- data/lib/exchange/cache.rb +2 -0
- data/lib/exchange/cache/base.rb +9 -5
- data/lib/exchange/cache/file.rb +65 -0
- data/lib/exchange/cache/memcached.rb +4 -4
- data/lib/exchange/cache/no_cache.rb +33 -0
- data/lib/exchange/cache/rails.rb +2 -2
- data/lib/exchange/cache/redis.rb +5 -5
- data/lib/exchange/configuration.rb +23 -7
- data/lib/exchange/currency.rb +94 -40
- data/lib/exchange/external_api.rb +1 -0
- data/lib/exchange/external_api/base.rb +11 -19
- data/lib/exchange/external_api/call.rb +10 -12
- data/lib/exchange/external_api/currency_bot.rb +2 -2
- data/lib/exchange/external_api/ecb.rb +68 -0
- data/lib/exchange/external_api/xavier_media.rb +4 -3
- data/lib/exchange/helper.rb +27 -0
- data/lib/exchange/iso_4217.rb +95 -0
- data/spec/core_extensions/conversability_spec.rb +40 -6
- data/spec/exchange/cache/base_spec.rb +4 -4
- data/spec/exchange/cache/file_spec.rb +70 -0
- data/spec/exchange/cache/memcached_spec.rb +5 -2
- data/spec/exchange/cache/no_cache_spec.rb +27 -0
- data/spec/exchange/cache/rails_spec.rb +6 -3
- data/spec/exchange/cache/redis_spec.rb +5 -2
- data/spec/exchange/currency_spec.rb +86 -23
- data/spec/exchange/external_api/base_spec.rb +8 -5
- data/spec/exchange/external_api/call_spec.rb +38 -29
- data/spec/exchange/external_api/currency_bot_spec.rb +8 -10
- data/spec/exchange/external_api/ecb_spec.rb +55 -0
- data/spec/exchange/external_api/xavier_media_spec.rb +8 -8
- data/spec/exchange/helper_spec.rb +30 -0
- data/spec/exchange/iso_4217_spec.rb +45 -0
- data/spec/support/api_responses/example_ecb_xml_90d.xml +64 -0
- data/spec/support/api_responses/example_ecb_xml_daily.xml +44 -0
- data/spec/support/api_responses/example_ecb_xml_history.xml +64 -0
- metadata +35 -21
@@ -1,4 +1,5 @@
|
|
1
1
|
module Exchange
|
2
|
+
|
2
3
|
# The external API module. Every class Handling an API has to be placed here and inherit from base. It has to call an api and define
|
3
4
|
# a rates hash, an exchange base and a unix timestamp. The call will get cached automatically with the right structure
|
4
5
|
# Allows for easy extension with an own api, as shown below
|
@@ -15,7 +16,7 @@ module Exchange
|
|
15
16
|
# # Every instance of ExternalAPI Class has to have an update function which gets the rates from the API
|
16
17
|
# def update(opts={})
|
17
18
|
# # assure that you will get a Time object for the historical dates
|
18
|
-
# time = assure_time(opts[:at])
|
19
|
+
# time = Exchange::Helper.assure_time(opts[:at])
|
19
20
|
#
|
20
21
|
# # call your API (shown here with a helper function that builds your API URL). Like this, your calls will get cached.
|
21
22
|
# Call.new(api_url(time), :at => time) do |result|
|
@@ -76,8 +77,14 @@ module Exchange
|
|
76
77
|
# Exchange::ExternalAPI::Base.new.rate(:usd, :eur, :at => Time.gm(3,23,2009))
|
77
78
|
# #=> 1.232231231
|
78
79
|
def rate(from, to, opts={})
|
79
|
-
|
80
|
-
|
80
|
+
rate = Configuration.cache_class.cached(Exchange::Configuration.api, opts.merge(:key_for => [from, to])) do
|
81
|
+
update(opts)
|
82
|
+
rate_from = self.rates[to.to_s.upcase]
|
83
|
+
rate_to = self.rates[from.to_s.upcase]
|
84
|
+
raise NoRateError.new("No rates where found for #{from} to #{to} #{'at ' + opts[:at].to_s if opts[:at]}") unless rate_from && rate_to
|
85
|
+
rate_from / rate_to
|
86
|
+
end
|
87
|
+
BigDecimal.new(rate.to_s)
|
81
88
|
end
|
82
89
|
|
83
90
|
# Converts an amount of one currency into another
|
@@ -91,23 +98,8 @@ module Exchange
|
|
91
98
|
# Exchange::ExternalAPI::Base.new.convert(23, :eur, :chf, :at => Time.gm(12,1,2011))
|
92
99
|
# #=> 30.12
|
93
100
|
def convert(amount, from, to, opts={})
|
94
|
-
|
101
|
+
amount * rate(from, to, opts)
|
95
102
|
end
|
96
|
-
|
97
|
-
protected
|
98
|
-
|
99
|
-
# A helper function to assure a value is an instance of time
|
100
|
-
# @param [Time, String, NilClass] The value to be asserted
|
101
|
-
# @param [Hash] opts Options for assertion
|
102
|
-
# @option opts [Symbol] :default If the argument is nil, you can define :default as :now to be delivered with Time.now instead of nil
|
103
|
-
|
104
|
-
def assure_time(arg=nil, opts={})
|
105
|
-
if arg
|
106
|
-
arg.kind_of?(Time) ? arg : Time.gm(*arg.split('-'))
|
107
|
-
elsif opts[:default] == :now
|
108
|
-
Time.now
|
109
|
-
end
|
110
|
-
end
|
111
103
|
|
112
104
|
end
|
113
105
|
end
|
@@ -12,6 +12,7 @@ module Exchange
|
|
12
12
|
# @param [String] url The url of the API to call
|
13
13
|
# @param [Hash] options The options of the API call
|
14
14
|
# @option options [Time] :at The time of the historical exchange rate file to get
|
15
|
+
# @option options [Class] :api The class to generate the key for
|
15
16
|
# @option options [Integer] :retries The number of retries if the API Call should fail with a HTTP Error
|
16
17
|
# @option options [Array] :retry_with an Array of urls to retry the call with (if the API does not have a file for the specified date). These values will be shifted until a call succeeds or the number of maximum retries is reached.
|
17
18
|
# @option options [Symbol] :format The format to return / yield the API call result in, defaults to :json
|
@@ -30,19 +31,14 @@ module Exchange
|
|
30
31
|
# # Do something with that result
|
31
32
|
|
32
33
|
def initialize url, options={}, &block
|
33
|
-
|
34
|
-
|
35
|
-
load_url(url, options[:retries] || 5, options[:retry_with])
|
36
|
-
end
|
37
|
-
else
|
38
|
-
result = load_url(url, options[:retries] || 5, options[:retry_with])
|
34
|
+
result = Configuration.cache_class(options).cached(options[:api] || Configuration.api, options) do
|
35
|
+
load_url(url, options[:retries] || Exchange::Configuration.retries, options[:retry_with])
|
39
36
|
end
|
40
37
|
|
41
38
|
parsed = options[:format] == :xml ? Nokogiri.parse(result) : JSON.load(result)
|
42
39
|
|
43
40
|
return parsed unless block_given?
|
44
|
-
|
45
|
-
yield parsed
|
41
|
+
yield parsed
|
46
42
|
end
|
47
43
|
|
48
44
|
private
|
@@ -52,20 +48,22 @@ module Exchange
|
|
52
48
|
# @param [Integer] retries The number of retries to do if the API Call should fail with a HTTP Error
|
53
49
|
# @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
|
54
50
|
|
55
|
-
def load_url(url, retries, retry_with)
|
56
|
-
begin
|
51
|
+
def load_url(url, retries, retry_with)
|
52
|
+
begin
|
57
53
|
result = URI.parse(url).open.read
|
58
54
|
rescue SocketError
|
55
|
+
puts 'SocketError'
|
59
56
|
raise APIError.new("Calling API #{url} produced a socket error")
|
60
|
-
rescue OpenURI::HTTPError
|
57
|
+
rescue OpenURI::HTTPError => e
|
58
|
+
retries -= 1
|
61
59
|
if retries > 0
|
62
|
-
retries -= 1
|
63
60
|
url = retry_with.shift if retry_with && !retry_with.empty?
|
64
61
|
retry
|
65
62
|
else
|
66
63
|
raise APIError.new("API #{url} was not reachable")
|
67
64
|
end
|
68
65
|
end
|
66
|
+
result
|
69
67
|
end
|
70
68
|
|
71
69
|
end
|
@@ -20,11 +20,11 @@ module Exchange
|
|
20
20
|
# Exchange::ExternalAPI::CurrencyBot.new.update(:at => Time.gm(3,2,2010))
|
21
21
|
|
22
22
|
def update(opts={})
|
23
|
-
time = assure_time(opts[:at])
|
23
|
+
time = Exchange::Helper.assure_time(opts[:at])
|
24
24
|
|
25
25
|
Call.new(api_url(time), :at => time) do |result|
|
26
26
|
@base = result['base']
|
27
|
-
@rates = result['rates']
|
27
|
+
@rates = Hash[*result['rates'].keys.zip(result['rates'].values.map{|v| BigDecimal.new(v.to_s) }).flatten]
|
28
28
|
@timestamp = result['timestamp'].to_i
|
29
29
|
end
|
30
30
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Exchange
|
2
|
+
module ExternalAPI
|
3
|
+
|
4
|
+
# The ECB class, handling communication with the European Central Bank XML File API
|
5
|
+
# You can find further information on the European Central Bank XML API API here: http://www.ecb.int/stats/exchange/eurofxref/html/index.en.html
|
6
|
+
# @author Beat Richartz
|
7
|
+
# @version 0.3
|
8
|
+
# @since 0.3
|
9
|
+
|
10
|
+
class ECB < Base
|
11
|
+
# The base of the ECB API URL
|
12
|
+
API_URL = "http://www.ecb.europa.eu/stats/eurofxref"
|
13
|
+
# The currencies the ECB API URL can handle
|
14
|
+
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
|
+
|
16
|
+
attr_accessor :callresult
|
17
|
+
|
18
|
+
# Updates the rates by getting the information from ECB API for today or a defined historical date
|
19
|
+
# The call gets cached for a maximum of 24 hours. Getting history from ECB is a bit special, since they do not seem to have
|
20
|
+
# 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
|
+
# @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 currency bot API to use the file of March 2, 2010
|
24
|
+
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
25
|
+
|
26
|
+
def update(opts={})
|
27
|
+
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 Configuration.cache
|
32
|
+
|
33
|
+
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
|
+
t = time
|
36
|
+
while (r = result.css("Cube[time=\"#{t.strftime("%Y-%m-%d")}\"]")).empty? && !times.empty?
|
37
|
+
t = times.shift
|
38
|
+
end
|
39
|
+
@callresult = r.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
parsed = Nokogiri.parse(self.callresult)
|
44
|
+
|
45
|
+
@base = 'EUR' # We just have to assume, since it's the ECB
|
46
|
+
@rates = Hash[*(['EUR', BigDecimal.new("1")] + parsed.children.children.map {|c| c.attributes.values.map{|v| v.value.match(/\d/) ? BigDecimal.new(v.value) : v.value }.sort_by(&:to_s).reverse unless c.attributes.values.empty? }.compact.flatten)]
|
47
|
+
@timestamp = time.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# A helper function which build a valid api url for the specified time
|
53
|
+
# If the date is today, get the small daily file. If it is less than 90 days ago, get the 90 days file.
|
54
|
+
# If it is more than 90 days ago, get the big file
|
55
|
+
# @param [Time] time The exchange rate date for which the URL should be built
|
56
|
+
# @return [String] An ECB API URL to get the xml from
|
57
|
+
|
58
|
+
def api_url(time)
|
59
|
+
border = Time.now - 90 * 86400
|
60
|
+
[
|
61
|
+
API_URL,
|
62
|
+
border <= time ? 'eurofxref-hist-90d.xml' : 'eurofxref-hist.xml'
|
63
|
+
].join('/')
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -15,19 +15,20 @@ module Exchange
|
|
15
15
|
|
16
16
|
# Updates the rates by getting the information from Xaviermedia API for today or a defined historical date
|
17
17
|
# The call gets cached for a maximum of 24 hours.
|
18
|
+
# @version 0.3
|
18
19
|
# @param [Hash] opts Options to define for the API Call
|
19
20
|
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
20
21
|
# @example Update the currency bot API to use the file of March 2, 2010
|
21
22
|
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
22
23
|
|
23
24
|
def update(opts={})
|
24
|
-
time = assure_time(opts[:at], :default => :now)
|
25
|
+
time = Exchange::Helper.assure_time(opts[:at], :default => :now)
|
25
26
|
api_url = api_url(time)
|
26
|
-
retry_urls =
|
27
|
+
retry_urls = Exchange::Configuration.retries.times.map{ |i| api_url(time - 86400 * (i+1)) }
|
27
28
|
|
28
29
|
Call.new(api_url, :format => :xml, :at => time, :retry_with => retry_urls) do |result|
|
29
30
|
@base = result.css('basecurrency').children[0].to_s
|
30
|
-
@rates = Hash[*result.css('fx currency_code').children.map(&:to_s).zip(result.css('fx rate').children.map{|c| c.to_s
|
31
|
+
@rates = Hash[*result.css('fx currency_code').children.map(&:to_s).zip(result.css('fx rate').children.map{|c| BigDecimal.new(c.to_s) }).flatten]
|
31
32
|
@timestamp = Time.gm(*result.css('fx_date').children[0].to_s.split('-')).to_i
|
32
33
|
end
|
33
34
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Exchange
|
2
|
+
# Helper Functions that get used throughout the gem can be placed here
|
3
|
+
# @author Beat Richartz
|
4
|
+
# @version 0.3
|
5
|
+
# @since 0.3
|
6
|
+
|
7
|
+
class Helper
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# A helper function to assure a value is an instance of time
|
11
|
+
# @param [Time, String, NilClass] The value to be asserted
|
12
|
+
# @param [Hash] opts Options for assertion
|
13
|
+
# @option opts [Symbol] :default a method that can be sent to Time if the argument is nil (:now for example)
|
14
|
+
|
15
|
+
def assure_time(arg=nil, opts={})
|
16
|
+
if arg
|
17
|
+
arg.kind_of?(Time) ? arg : Time.gm(*arg.split('-'))
|
18
|
+
elsif opts[:default]
|
19
|
+
Time.send(opts[:default])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Exchange
|
2
|
+
|
3
|
+
# This class handles everything that has to do with certified formatting of the different currencies. The standard is stored in
|
4
|
+
# the iso4217 YAML file.
|
5
|
+
# @version 0.3
|
6
|
+
# @since 0.3
|
7
|
+
# @author Beat Richartz
|
8
|
+
|
9
|
+
class ISO4217
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# The ISO 4217 that have to be loaded. Nothing much to say here. Just use this method to get to the definitions
|
13
|
+
# They are static, so they can be stored in a class variable without many worries
|
14
|
+
# @return [Hash] The iso427 Definitions with the currency code as keys
|
15
|
+
|
16
|
+
def definitions
|
17
|
+
@@definitions ||= YAML.load_file('iso4217.yml')
|
18
|
+
end
|
19
|
+
|
20
|
+
# Use this to instantiate a currency amount. For one, it is important that we use BigDecimal here so nothing gets lost because
|
21
|
+
# of floating point errors. For the other, This allows us to set the precision exactly according to the iso definition
|
22
|
+
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to instantiate
|
23
|
+
# @param [String, Symbol] currency The currency you want to instantiate the money in
|
24
|
+
# @return [BigDecimal] The instantiated currency
|
25
|
+
# @example instantiate a currency from a string
|
26
|
+
# Exchange::ISO4217.instantiate("4523", "usd") #=> #<Bigdecimal 4523.00>
|
27
|
+
|
28
|
+
def instantiate(amount, currency)
|
29
|
+
BigDecimal.new(amount.to_s, definitions[currency.to_s.upcase]['minor_unit'])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
|
33
|
+
# with no worries how to display the currency.
|
34
|
+
# @param [BigDecimal, Fixed, Float] amount The amount of currency you want to stringify
|
35
|
+
# @param [String, Symbol] currency The currency you want to stringify
|
36
|
+
# @param [Hash] opts The options for formatting
|
37
|
+
# @option opts [Boolean] :amount_only Whether you want to have the currency in the string or not
|
38
|
+
# @return [String] The formatted string
|
39
|
+
# @example Convert a currency to a string
|
40
|
+
# Exchange::ISO4217.stringify(49.567, :usd) #=> "USD 49.57"
|
41
|
+
# @example Convert a currency without minor to a string
|
42
|
+
# Exchange::ISO4217.stringif(45, :jpy) #=> "JPY 45"
|
43
|
+
# @example Convert a currency with a three decimal minor to a string
|
44
|
+
# Exchange::ISO4217.stringif(34.34, :omr) #=> "OMR 34.340"
|
45
|
+
# @example Convert a currency to a string without the currency
|
46
|
+
# Exchange::ISO4217.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
|
47
|
+
|
48
|
+
def stringify(amount, currency, opts={})
|
49
|
+
format = "%.#{definitions[currency.to_s.upcase]['minor_unit']}f"
|
50
|
+
"#{currency.to_s.upcase + ' ' unless opts[:amount_only]}#{format % amount}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
# @private
|
55
|
+
# @macro [attach] install_operations
|
56
|
+
|
57
|
+
def install_operation op
|
58
|
+
self.class_eval <<-EOV
|
59
|
+
def self.#{op}(amount, currency, precision=nil)
|
60
|
+
minor = definitions[currency.to_s.upcase]['minor_unit']
|
61
|
+
(amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, minor)).#{op}(precision || minor)
|
62
|
+
end
|
63
|
+
EOV
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Use this to round a currency amount. This allows us to round exactly to the number of minors the currency has in the
|
68
|
+
# iso definition
|
69
|
+
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to round
|
70
|
+
# @param [String, Symbol] currency The currency you want to round the money in
|
71
|
+
# @example Round a currency with 2 minors
|
72
|
+
# Exchange::ISO4217.round("4523.456", "usd") #=> #<Bigdecimal 4523.46>
|
73
|
+
|
74
|
+
install_operation :round
|
75
|
+
|
76
|
+
# Use this to ceil a currency amount. This allows us to ceil exactly to the number of minors the currency has in the
|
77
|
+
# iso definition
|
78
|
+
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to ceil
|
79
|
+
# @param [String, Symbol] currency The currency you want to ceil the money in
|
80
|
+
# @example Ceil a currency with 2 minors
|
81
|
+
# Exchange::ISO4217.ceil("4523.456", "usd") #=> #<Bigdecimal 4523.46>
|
82
|
+
|
83
|
+
install_operation :ceil
|
84
|
+
|
85
|
+
# Use this to floor a currency amount. This allows us to floor exactly to the number of minors the currency has in the
|
86
|
+
# iso definition
|
87
|
+
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to floor
|
88
|
+
# @param [String, Symbol] currency The currency you want to floor the money in
|
89
|
+
# @example Floor a currency with 2 minors
|
90
|
+
# Exchange::ISO4217.floor("4523.456", "usd") #=> #<Bigdecimal 4523.46>
|
91
|
+
|
92
|
+
install_operation :floor
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -7,6 +7,13 @@ describe "Exchange::Conversability" do
|
|
7
7
|
after(:all) do
|
8
8
|
Exchange::Configuration.cache = :memcached
|
9
9
|
end
|
10
|
+
it "should define all currencies on Fixnum, Float and BigDecimal" do
|
11
|
+
Exchange::ISO4217.definitions.keys.each do |c|
|
12
|
+
1.should be_respond_to(c.downcase.to_sym)
|
13
|
+
1.1.should be_respond_to(c.downcase.to_sym)
|
14
|
+
BigDecimal.new("1").should be_respond_to(c.downcase.to_sym)
|
15
|
+
end
|
16
|
+
end
|
10
17
|
context "with a fixnum" do
|
11
18
|
it "should allow to convert to a currency" do
|
12
19
|
3.eur.should be_kind_of Exchange::Currency
|
@@ -19,13 +26,13 @@ describe "Exchange::Conversability" do
|
|
19
26
|
it "should allow to do full conversions" do
|
20
27
|
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
21
28
|
3.eur.to_chf.should be_kind_of Exchange::Currency
|
22
|
-
3.eur.to_chf.value.should == 3.62
|
29
|
+
3.eur.to_chf.value.round(2).should == 3.62
|
23
30
|
3.eur.to_chf.currency.should == 'chf'
|
24
31
|
end
|
25
32
|
it "should allow to do full conversions with negative numbers" do
|
26
33
|
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
27
34
|
-3.eur.to_chf.should be_kind_of Exchange::Currency
|
28
|
-
-3.eur.to_chf.value.should == -3.62
|
35
|
+
-3.eur.to_chf.value.round(2).should == -3.62
|
29
36
|
-3.eur.to_chf.currency.should == 'chf'
|
30
37
|
end
|
31
38
|
it "should allow to define a historic time in which the currency should be interpreted" do
|
@@ -37,22 +44,22 @@ describe "Exchange::Conversability" do
|
|
37
44
|
context "with a float" do
|
38
45
|
it "should allow to convert to a currency" do
|
39
46
|
3.25.eur.should be_kind_of Exchange::Currency
|
40
|
-
3.25.eur.value.should == 3.25
|
47
|
+
3.25.eur.value.round(2).should == 3.25
|
41
48
|
end
|
42
49
|
it "should allow to convert to a curreny with a negative number" do
|
43
50
|
-3.25.eur.should be_kind_of Exchange::Currency
|
44
|
-
-3.25.eur.value.should == -3.25
|
51
|
+
-3.25.eur.value.round(2).should == -3.25
|
45
52
|
end
|
46
53
|
it "should allow to do full conversions" do
|
47
54
|
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
48
55
|
3.25.eur.to_chf.should be_kind_of Exchange::Currency
|
49
|
-
3.25.eur.to_chf.value.should == 3.92
|
56
|
+
3.25.eur.to_chf.value.round(2).should == 3.92
|
50
57
|
3.25.eur.to_chf.currency.should == 'chf'
|
51
58
|
end
|
52
59
|
it "should allow to do full conversions with negative numbers" do
|
53
60
|
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
54
61
|
-3.25.eur.to_chf.should be_kind_of Exchange::Currency
|
55
|
-
-3.25.eur.to_chf.value.should == -3.92
|
62
|
+
-3.25.eur.to_chf.value.round(2).should == -3.92
|
56
63
|
-3.25.eur.to_chf.currency.should == 'chf'
|
57
64
|
end
|
58
65
|
it "should allow to define a historic time in which the currency should be interpreted" do
|
@@ -61,4 +68,31 @@ describe "Exchange::Conversability" do
|
|
61
68
|
3.25.chf(:at => '2010-01-01').time.year.should == 2010
|
62
69
|
end
|
63
70
|
end
|
71
|
+
context "with a big decimal" do
|
72
|
+
it "should allow to convert to a currency" do
|
73
|
+
BigDecimal.new("3.25").eur.should be_kind_of Exchange::Currency
|
74
|
+
BigDecimal.new("3.25").eur.value.round(2).should == 3.25
|
75
|
+
end
|
76
|
+
it "should allow to convert to a curreny with a negative number" do
|
77
|
+
BigDecimal.new("-3.25").eur.should be_kind_of Exchange::Currency
|
78
|
+
BigDecimal.new("-3.25").eur.value.round(2).should == -3.25
|
79
|
+
end
|
80
|
+
it "should allow to do full conversions" do
|
81
|
+
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
82
|
+
BigDecimal.new("3.25").eur.to_chf.should be_kind_of Exchange::Currency
|
83
|
+
BigDecimal.new("3.25").eur.to_chf.value.round(2).should == 3.92
|
84
|
+
BigDecimal.new("3.25").eur.to_chf.currency.should == 'chf'
|
85
|
+
end
|
86
|
+
it "should allow to do full conversions with negative numbers" do
|
87
|
+
mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
|
88
|
+
BigDecimal.new("-3.25").eur.to_chf.should be_kind_of Exchange::Currency
|
89
|
+
BigDecimal.new("-3.25").eur.to_chf.value.round(2).should == -3.92
|
90
|
+
BigDecimal.new("-3.25").eur.to_chf.currency.should == 'chf'
|
91
|
+
end
|
92
|
+
it "should allow to define a historic time in which the currency should be interpreted" do
|
93
|
+
BigDecimal.new("3.25").chf(:at => Time.gm(2010,1,1)).time.yday.should == 1
|
94
|
+
BigDecimal.new("3.25").chf(:at => Time.gm(2010,1,1)).time.year.should == 2010
|
95
|
+
BigDecimal.new("3.25").chf(:at => '2010-01-01').time.year.should == 2010
|
96
|
+
end
|
97
|
+
end
|
64
98
|
end
|
@@ -9,8 +9,8 @@ describe "Exchange::Cache::Base" do
|
|
9
9
|
end
|
10
10
|
context "with a daily cache" do
|
11
11
|
it "should build a timestamped key with the class given, the yearday and the year" do
|
12
|
-
Exchange::Cache::Base.send(:key,
|
13
|
-
Exchange::Cache::Base.send(:key,
|
12
|
+
Exchange::Cache::Base.send(:key, :xavier_media).should == 'exchange_xavier_media_2012_61'
|
13
|
+
Exchange::Cache::Base.send(:key, :currency_bot).should == 'exchange_currency_bot_2012_61'
|
14
14
|
end
|
15
15
|
end
|
16
16
|
context "with an hourly cache" do
|
@@ -21,8 +21,8 @@ describe "Exchange::Cache::Base" do
|
|
21
21
|
Exchange::Configuration.update = :daily
|
22
22
|
end
|
23
23
|
it "should build a timestamped key with the class given, the yearday, the year and the hour" do
|
24
|
-
Exchange::Cache::Base.send(:key,
|
25
|
-
Exchange::Cache::Base.send(:key,
|
24
|
+
Exchange::Cache::Base.send(:key, :xavier_media).should == 'exchange_xavier_media_2012_61_23'
|
25
|
+
Exchange::Cache::Base.send(:key, :currency_bot).should == 'exchange_currency_bot_2012_61_23'
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|