exchange 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -4
- data/README.rdoc +1 -1
- data/lib/exchange/base.rb +2 -2
- data/lib/exchange/cache/base.rb +10 -2
- data/lib/exchange/cache/configuration.rb +20 -1
- data/lib/exchange/cache/file.rb +6 -3
- data/lib/exchange/cache/memcached.rb +3 -1
- data/lib/exchange/cache/memory.rb +1 -1
- data/lib/exchange/cache/rails.rb +1 -1
- data/lib/exchange/cache/redis.rb +3 -1
- data/lib/exchange/configurable.rb +3 -0
- data/lib/exchange/core_extensions/cachify.rb +4 -0
- data/lib/exchange/external_api/base.rb +2 -2
- data/lib/exchange/external_api/call.rb +1 -1
- data/lib/exchange/external_api/configuration.rb +8 -0
- data/lib/exchange/external_api/ecb.rb +2 -2
- data/lib/exchange/external_api/open_exchange_rates.rb +2 -2
- data/lib/exchange/external_api/random.rb +2 -2
- data/lib/exchange/external_api/xavier_media.rb +6 -6
- data/lib/exchange/helper.rb +1 -1
- data/lib/exchange/iso.rb +17 -10
- data/lib/exchange/money.rb +21 -12
- data/spec/exchange/iso_spec.rb +16 -0
- data/spec/exchange/money_spec.rb +18 -4
- metadata +18 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a30e01a5f55d57a9201b8da420a8c3849ca73c6
|
4
|
+
data.tar.gz: 3fde15afd9be50c593edab5bf94f517cc55e24b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef1eafc5d5d39a7e68508285e6c2b1dfd194034fd11792106180d63265b1ad500d0c362f2c77cb35aaa510f47412704559b09c47fbcd210afee3f398d319f2f6
|
7
|
+
data.tar.gz: 27dce37969266711bbba84ddb2777e78b25fc3bca17092364ca0baadb787fa92843904b2020224c338d2d47c2d67907a917caf3b7bbaa0fd0cdae670c4bb60ea
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
exchange
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/.travis.yml
CHANGED
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= exchange {<img src="https://secure.travis-ci.org/beatrichartz/exchange.png?branch=master" />}[http://travis-ci.org/beatrichartz/exchange] {<img src="https://gemnasium.com/beatrichartz/exchange.png" alt="Dependency Status" />}[https://gemnasium.com/beatrichartz/exchange] {<img src="https://codeclimate.com/
|
1
|
+
= exchange {<img src="https://secure.travis-ci.org/beatrichartz/exchange.png?branch=master" />}[http://travis-ci.org/beatrichartz/exchange] {<img src="https://gemnasium.com/beatrichartz/exchange.png" alt="Dependency Status" />}[https://gemnasium.com/beatrichartz/exchange] {<img src="https://codeclimate.com/github/beatrichartz/exchange.png" />}[https://codeclimate.com/github/beatrichartz/exchange]
|
2
2
|
|
3
3
|
The Exchange Gem gives you easy access to currency functions directly on your Numbers. {It is tested against}[http://travis-ci.org/beatrichartz/exchange]: ruby 2.0, 1.9, ruby 1.8, ree and rubinius (1.8 and 1.9 mode). Exchange will in future versions take advantage of 2.0 features, be sure to check this page for updates!
|
4
4
|
|
data/lib/exchange/base.rb
CHANGED
@@ -3,7 +3,7 @@ module Exchange
|
|
3
3
|
|
4
4
|
# The current version of the exchange gem
|
5
5
|
#
|
6
|
-
VERSION = '1.0
|
6
|
+
VERSION = '1.1.0'
|
7
7
|
|
8
8
|
# The root installation path of the gem
|
9
9
|
# @version 0.5
|
@@ -21,6 +21,6 @@ module Exchange
|
|
21
21
|
# @version 0.10
|
22
22
|
# @since 0.10
|
23
23
|
#
|
24
|
-
NoCurrencyError = Class.new
|
24
|
+
NoCurrencyError = Class.new ArgumentError
|
25
25
|
|
26
26
|
end
|
data/lib/exchange/cache/base.rb
CHANGED
@@ -48,7 +48,7 @@ module Exchange
|
|
48
48
|
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
49
49
|
#
|
50
50
|
def cached api, opts={}, &block
|
51
|
-
|
51
|
+
raise_caching_needs_block! unless block_given?
|
52
52
|
|
53
53
|
block.call
|
54
54
|
end
|
@@ -63,6 +63,8 @@ module Exchange
|
|
63
63
|
# Generates a key which can handle expiration by itself
|
64
64
|
# @param [Exchange::ExternalAPI::Subclass] api The API to store the data for
|
65
65
|
# @param [Hash] opts The options for caching
|
66
|
+
# @option opts [Array] :key_for An array of additional key elements
|
67
|
+
# @option opts [Time] :at The timestamp for the key
|
66
68
|
# @return [String] A string that can be used as cache key
|
67
69
|
# @example
|
68
70
|
# Exchange::Cache::Base.key(Exchange::ExternalAPI::OpenExchangeRates, Time.gm(2012,1,1)) #=> "Exchange_ExternalAPI_CurrencyBot_2012_1"
|
@@ -91,6 +93,12 @@ module Exchange
|
|
91
93
|
def helper
|
92
94
|
@helper ||= Exchange::Helper
|
93
95
|
end
|
96
|
+
|
97
|
+
# Raise a caching needs a block error
|
98
|
+
#
|
99
|
+
def raise_caching_needs_block!
|
100
|
+
raise CachingWithoutBlockError.new('Caching needs a block')
|
101
|
+
end
|
94
102
|
|
95
103
|
end
|
96
104
|
|
@@ -98,6 +106,6 @@ module Exchange
|
|
98
106
|
# @since 0.1
|
99
107
|
# @version 0.1
|
100
108
|
#
|
101
|
-
CachingWithoutBlockError = Class.new
|
109
|
+
CachingWithoutBlockError = Class.new ArgumentError
|
102
110
|
end
|
103
111
|
end
|
@@ -8,10 +8,16 @@ module Exchange
|
|
8
8
|
# @since 0.9
|
9
9
|
#
|
10
10
|
class Configuration < Exchange::Configurable
|
11
|
+
|
12
|
+
# Additional properties which are proprietary to the cache configuration
|
13
|
+
#
|
11
14
|
attr_accessor :expire, :host, :port, :path
|
12
15
|
|
13
16
|
class << self
|
14
17
|
|
18
|
+
# Alias method chain to set the client to nil before an attribute of the configuration is set.
|
19
|
+
# @param setters The attribute names for which the chain has to be installed
|
20
|
+
#
|
15
21
|
def wipe_client_before_setting *setters
|
16
22
|
|
17
23
|
setters.each do |setter|
|
@@ -27,26 +33,39 @@ module Exchange
|
|
27
33
|
|
28
34
|
end
|
29
35
|
|
36
|
+
# delegate all necessary methods to the instance
|
37
|
+
#
|
30
38
|
def_delegators :instance, :expire, :expire=, :host, :host=, :port, :port=, :path, :path=
|
39
|
+
|
40
|
+
# set the client to nil before setting the host or the port
|
41
|
+
#
|
31
42
|
wipe_client_before_setting :host, :port
|
32
43
|
|
33
|
-
# Overrides the parent class method to
|
44
|
+
# Overrides the parent class method to set the client to nil before setting the configuration via a hash
|
45
|
+
# @param [Hash] hash The hash with the configuration options to set the configuration to
|
34
46
|
#
|
35
47
|
def set hash
|
36
48
|
wipe_subclass_client!
|
37
49
|
super
|
38
50
|
end
|
39
51
|
|
52
|
+
# The parent module to get the constants from
|
53
|
+
# @returns [Class] the Cache class, always
|
54
|
+
#
|
40
55
|
def parent_module
|
41
56
|
Cache
|
42
57
|
end
|
43
58
|
|
59
|
+
# The key of the configuration
|
60
|
+
#
|
44
61
|
def key
|
45
62
|
:cache
|
46
63
|
end
|
47
64
|
|
48
65
|
private
|
49
66
|
|
67
|
+
# set the client instance variable to nil if possible
|
68
|
+
#
|
50
69
|
def wipe_subclass_client!
|
51
70
|
subclass.wipe_client! if subclass && subclass.respond_to?(:wipe_client!)
|
52
71
|
end
|
data/lib/exchange/cache/file.rb
CHANGED
@@ -3,9 +3,10 @@ module Exchange
|
|
3
3
|
module Cache
|
4
4
|
|
5
5
|
# @author Beat Richartz
|
6
|
-
# A class that allows to store api call results in files.
|
6
|
+
# A class that allows to store api call results in files.
|
7
7
|
# It just may be necessary to cache large files somewhere, this class allows you to do that
|
8
|
-
#
|
8
|
+
# @note This is not a recommended caching option
|
9
|
+
#
|
9
10
|
# @version 0.6
|
10
11
|
# @since 0.3
|
11
12
|
|
@@ -21,6 +22,8 @@ module Exchange
|
|
21
22
|
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
22
23
|
#
|
23
24
|
def cached api, opts={}, &block
|
25
|
+
raise_caching_needs_block! unless block_given?
|
26
|
+
|
24
27
|
today = Time.now
|
25
28
|
dir = config.path
|
26
29
|
path = ::File.join(dir, key(api, opts[:cache_period]))
|
@@ -50,7 +53,7 @@ module Exchange
|
|
50
53
|
# @example
|
51
54
|
# Exchange::Cache::Base.key(Exchange::ExternalAPI::OpenExchangeRates, :monthly) #=> "Exchange_ExternalAPI_CurrencyBot_monthly_2012_1"
|
52
55
|
#
|
53
|
-
def key
|
56
|
+
def key api_class, cache_period=:daily
|
54
57
|
time = Time.now
|
55
58
|
[api_class.to_s.gsub(/::/, '_'), cache_period, time.year, time.send(cache_period == :monthly ? :month : :yday)].join('_')
|
56
59
|
end
|
@@ -46,7 +46,7 @@ module Exchange
|
|
46
46
|
# @param [Hash] opts The options for caching
|
47
47
|
# @return [String] A string that can be used as instance variable name
|
48
48
|
#
|
49
|
-
def instance_variable_name
|
49
|
+
def instance_variable_name api, opts
|
50
50
|
conversion_time = helper.assure_time(opts[:at], :default => :now)
|
51
51
|
time = Time.now
|
52
52
|
expire_hourly = config.expire == :hourly || nil
|
data/lib/exchange/cache/rails.rb
CHANGED
@@ -33,7 +33,7 @@ module Exchange
|
|
33
33
|
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
34
34
|
#
|
35
35
|
def cached api, opts={}, &block
|
36
|
-
|
36
|
+
raise_caching_needs_block! unless block_given?
|
37
37
|
|
38
38
|
result = client.fetch key(api, opts), :expires_in => config.expire == :daily ? 86400 : 3600, &block
|
39
39
|
client.delete(key(api, opts)) unless result && !result.to_s.empty?
|
data/lib/exchange/cache/redis.rb
CHANGED
@@ -14,6 +14,8 @@ module Exchange
|
|
14
14
|
# end
|
15
15
|
class Redis < Base
|
16
16
|
|
17
|
+
# delegate the client and wiping the client to the instance
|
18
|
+
#
|
17
19
|
def_delegators :instance, :client, :wipe_client!
|
18
20
|
|
19
21
|
# instantiates a redis client and memoizes it in a class variable.
|
@@ -41,7 +43,7 @@ module Exchange
|
|
41
43
|
# @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
|
42
44
|
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
43
45
|
#
|
44
|
-
def cached api, opts={}, &block
|
46
|
+
def cached api, opts={}, &block
|
45
47
|
if result = client.get(key(api, opts))
|
46
48
|
result = opts[:plain] ? result : result.decachify
|
47
49
|
else
|
@@ -12,6 +12,9 @@ module Exchange
|
|
12
12
|
|
13
13
|
def_delegators :instance, :subclass, :subclass=, :set
|
14
14
|
|
15
|
+
# Alias method chain to instantiate the subclass from a symbol should it not be a class
|
16
|
+
# @return [NilClass, Class] The subclass or nil
|
17
|
+
#
|
15
18
|
def subclass_with_constantize
|
16
19
|
self.subclass = parent_module.const_get camelize(self.subclass_without_constantize) unless !self.subclass_without_constantize || self.subclass_without_constantize.is_a?(Class)
|
17
20
|
subclass_without_constantize
|
@@ -2,6 +2,8 @@
|
|
2
2
|
module Exchange
|
3
3
|
module Cachify
|
4
4
|
|
5
|
+
# Use cachify as an alias for Marshal dumping
|
6
|
+
#
|
5
7
|
def cachify
|
6
8
|
Marshal.dump self
|
7
9
|
end
|
@@ -10,6 +12,8 @@ module Exchange
|
|
10
12
|
|
11
13
|
module Decachify
|
12
14
|
|
15
|
+
# Use cachify as an alias for Marshal loading
|
16
|
+
#
|
13
17
|
def decachify
|
14
18
|
Marshal.load self
|
15
19
|
end
|
@@ -124,7 +124,7 @@ module Exchange
|
|
124
124
|
# Exchange::ExternalAPI::Base.new.rate(:usd, :eur, :at => Time.gm(3,23,2009))
|
125
125
|
# #=> 1.232231231
|
126
126
|
#
|
127
|
-
def rate
|
127
|
+
def rate from, to, opts={}
|
128
128
|
rate = cache.cached(self.class, opts.merge(:key_for => [from, to])) do
|
129
129
|
update(opts)
|
130
130
|
|
@@ -150,7 +150,7 @@ module Exchange
|
|
150
150
|
# Exchange::ExternalAPI::Base.new.convert(23, :eur, :chf, :at => Time.gm(12,1,2011))
|
151
151
|
# #=> 30.12
|
152
152
|
#
|
153
|
-
def convert
|
153
|
+
def convert amount, from, to, opts={}
|
154
154
|
amount * rate(from, to, opts)
|
155
155
|
end
|
156
156
|
|
@@ -52,7 +52,7 @@ module Exchange
|
|
52
52
|
# @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
|
53
53
|
# @todo install a timeout for slow requests, but respect when loading large files
|
54
54
|
#
|
55
|
-
def load_url
|
55
|
+
def load_url url, retries, retry_with
|
56
56
|
begin
|
57
57
|
result = URI.parse(url).open.read
|
58
58
|
rescue SocketError
|
@@ -13,6 +13,8 @@ module Exchange
|
|
13
13
|
|
14
14
|
def_delegators :instance, :retries, :retries=, :app_id, :app_id=, :protocol, :protocol=, :fallback, :fallback=
|
15
15
|
|
16
|
+
# Constantize fallback apis on the fly
|
17
|
+
#
|
16
18
|
def fallback_with_constantize
|
17
19
|
self.fallback = Array(fallback_without_constantize).map do |fb|
|
18
20
|
unless !fb || fb.is_a?(Class)
|
@@ -27,10 +29,16 @@ module Exchange
|
|
27
29
|
alias_method :fallback_without_constantize, :fallback
|
28
30
|
alias_method :fallback, :fallback_with_constantize
|
29
31
|
|
32
|
+
# The configuration parent module
|
33
|
+
# @return [Class] ExternalAPI, always
|
34
|
+
#
|
30
35
|
def parent_module
|
31
36
|
ExternalAPI
|
32
37
|
end
|
33
38
|
|
39
|
+
# The configuration key
|
40
|
+
# @return [Symbol] :api, always
|
41
|
+
#
|
34
42
|
def key
|
35
43
|
:api
|
36
44
|
end
|
@@ -32,7 +32,7 @@ module Exchange
|
|
32
32
|
# @since 0.1
|
33
33
|
# @version 0.7
|
34
34
|
#
|
35
|
-
def update
|
35
|
+
def update opts={}
|
36
36
|
time = helper.assure_time(opts[:at], :default => :now)
|
37
37
|
times = map_retry_times time
|
38
38
|
|
@@ -57,7 +57,7 @@ module Exchange
|
|
57
57
|
# @param [Time] time The exchange rate date for which the URL should be built
|
58
58
|
# @return [String] An ECB API URL to get the xml from
|
59
59
|
#
|
60
|
-
def api_url
|
60
|
+
def api_url time
|
61
61
|
border = Time.now - 90 * 86400
|
62
62
|
[ "#{config.protocol}:/",
|
63
63
|
API_URL,
|
@@ -24,7 +24,7 @@ module Exchange
|
|
24
24
|
# @example Update the Open Exchange Rates API to use the file of March 2, 2010
|
25
25
|
# Exchange::ExternalAPI::OpenExchangeRates.new.update(:at => Time.gm(3,2,2010))
|
26
26
|
#
|
27
|
-
def update
|
27
|
+
def update opts={}
|
28
28
|
time = helper.assure_time(opts[:at])
|
29
29
|
|
30
30
|
Call.new(api_url(time), :at => time, :api => self.class) do |result|
|
@@ -52,7 +52,7 @@ module Exchange
|
|
52
52
|
# @since 0.1
|
53
53
|
# @version 0.2.6
|
54
54
|
#
|
55
|
-
def api_url
|
55
|
+
def api_url time=nil
|
56
56
|
today = Time.now
|
57
57
|
[
|
58
58
|
"#{config.protocol}:/",
|
@@ -10,7 +10,7 @@ module Exchange
|
|
10
10
|
class Random < Base
|
11
11
|
|
12
12
|
CURRENCIES = Exchange::ISO.currencies
|
13
|
-
RANDOM_RATES = lambda { Hash[*CURRENCIES.
|
13
|
+
RANDOM_RATES = lambda { Hash[*CURRENCIES.map{|c| [c, rand] }.flatten] }
|
14
14
|
|
15
15
|
# Updates the rates with new random ones
|
16
16
|
# The call gets cached for a maximum of 24 hours.
|
@@ -20,7 +20,7 @@ module Exchange
|
|
20
20
|
# @example Update the currency bot API to use the file of March 2, 2010
|
21
21
|
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
22
22
|
#
|
23
|
-
def update
|
23
|
+
def update opts={}
|
24
24
|
@base = :usd
|
25
25
|
@rates = RANDOM_RATES.call
|
26
26
|
@timestamp = Time.now.to_i
|
@@ -23,7 +23,7 @@ module Exchange
|
|
23
23
|
# @example Update the currency bot API to use the file of March 2, 2010
|
24
24
|
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
25
25
|
#
|
26
|
-
def update
|
26
|
+
def update opts={}
|
27
27
|
time = helper.assure_time(opts[:at], :default => :now)
|
28
28
|
api_url = api_url(time)
|
29
29
|
|
@@ -40,7 +40,7 @@ module Exchange
|
|
40
40
|
# @param [Time] time The exchange rate date for which the URL should be built
|
41
41
|
# @return [String] An Xaviermedia API URL for the specified time
|
42
42
|
#
|
43
|
-
def api_url
|
43
|
+
def api_url time
|
44
44
|
["#{config.protocol}:/", API_URL, "#{time.strftime("%Y/%m/%d")}.xml"].join('/')
|
45
45
|
end
|
46
46
|
|
@@ -51,7 +51,7 @@ module Exchange
|
|
51
51
|
# @since 0.6
|
52
52
|
# @version 0.6
|
53
53
|
#
|
54
|
-
def api_opts
|
54
|
+
def api_opts opts={}
|
55
55
|
retry_urls = config.retries.times.map { |i| api_url(opts[:at] - 86400 * (i+1)) }
|
56
56
|
|
57
57
|
{ :format => :xml, :at => opts[:at], :retry_with => retry_urls }
|
@@ -63,7 +63,7 @@ module Exchange
|
|
63
63
|
# @since 0.7
|
64
64
|
# @version 0.7
|
65
65
|
#
|
66
|
-
def extract_timestamp
|
66
|
+
def extract_timestamp result
|
67
67
|
Time.gm(*result.css('fx_date').children[0].to_s.split('-')).to_i
|
68
68
|
end
|
69
69
|
|
@@ -73,7 +73,7 @@ module Exchange
|
|
73
73
|
# @since 0.7
|
74
74
|
# @version 0.7
|
75
75
|
#
|
76
|
-
def extract_rates
|
76
|
+
def extract_rates result
|
77
77
|
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
|
78
78
|
to_hash!(rates_array)
|
79
79
|
end
|
@@ -84,7 +84,7 @@ module Exchange
|
|
84
84
|
# @since 0.7
|
85
85
|
# @version 0.7
|
86
86
|
#
|
87
|
-
def extract_base_currency
|
87
|
+
def extract_base_currency result
|
88
88
|
result.css('basecurrency').children[0].to_s.downcase.to_sym
|
89
89
|
end
|
90
90
|
|
data/lib/exchange/helper.rb
CHANGED
@@ -18,7 +18,7 @@ module Exchange
|
|
18
18
|
# @param [Hash] opts Options for assertion
|
19
19
|
# @option opts [Symbol] :default a method that can be sent to Time if the argument is nil (:now for example)
|
20
20
|
#
|
21
|
-
def assure_time
|
21
|
+
def assure_time arg=nil, opts={}
|
22
22
|
if arg
|
23
23
|
arg.kind_of?(Time) ? arg : Time.gm(*arg.split('-'))
|
24
24
|
elsif opts[:default]
|
data/lib/exchange/iso.rb
CHANGED
@@ -23,7 +23,7 @@ module Exchange
|
|
23
23
|
|
24
24
|
def install_operation op
|
25
25
|
self.class_eval <<-EOV
|
26
|
-
def #{op}
|
26
|
+
def #{op} amount, currency, precision=nil, opts={}
|
27
27
|
minor = definitions[currency][:minor_unit]
|
28
28
|
money = amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, precision_for(amount, currency))
|
29
29
|
if opts[:psych] && minor > 0
|
@@ -87,7 +87,7 @@ module Exchange
|
|
87
87
|
# Exchange::ISO.instantiate("4523", "usd") #=> #<Bigdecimal 4523.00>
|
88
88
|
# @note Reinstantiation is not needed in case the amount is already a big decimal. In this case, the maximum precision is already given.
|
89
89
|
#
|
90
|
-
def instantiate
|
90
|
+
def instantiate amount, currency
|
91
91
|
if amount.is_a?(BigDecimal)
|
92
92
|
amount
|
93
93
|
else
|
@@ -111,18 +111,14 @@ module Exchange
|
|
111
111
|
# @example Convert a currency to a string without the currency
|
112
112
|
# Exchange::ISO.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
|
113
113
|
#
|
114
|
-
def stringify
|
114
|
+
def stringify amount, currency, opts={}
|
115
115
|
definition = definitions[currency]
|
116
116
|
separators = definition[:separators] || {}
|
117
117
|
format = "%.#{definition[:minor_unit]}f"
|
118
118
|
string = format % amount
|
119
119
|
major, minor = string.split('.')
|
120
|
-
|
121
|
-
if separators[:major] && opts[:format] != :plain
|
122
|
-
major.reverse!
|
123
|
-
major.gsub!(/(\d{3})(?=.)/) { $1 + separators[:major] }
|
124
|
-
major.reverse!
|
125
|
-
end
|
120
|
+
|
121
|
+
major.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/) { $1 + separators[:major] } if separators[:major] && opts[:format] != :plain
|
126
122
|
|
127
123
|
string = minor ? major + (opts[:format] == :plain || !separators[:minor] ? '.' : separators[:minor]) + minor : major
|
128
124
|
pre = [[:amount, :plain].include?(opts[:format]) && '', opts[:format] == :symbol && definition[:symbol], currency.to_s.upcase + ' '].detect{|a| a.is_a?(String)}
|
@@ -130,6 +126,14 @@ module Exchange
|
|
130
126
|
"#{pre}#{string}"
|
131
127
|
end
|
132
128
|
|
129
|
+
# Returns the symbol for a given currency. Returns nil if no symbol is present
|
130
|
+
# @param currency The currency to return the symbol for
|
131
|
+
# @return [String, NilClass] The symbol or nil
|
132
|
+
#
|
133
|
+
def symbol currency
|
134
|
+
definitions[currency][:symbol]
|
135
|
+
end
|
136
|
+
|
133
137
|
# Use this to round a currency amount. This allows us to round exactly to the number of minors the currency has in the
|
134
138
|
# iso definition
|
135
139
|
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to round
|
@@ -159,7 +163,7 @@ module Exchange
|
|
159
163
|
|
160
164
|
# Forwards the assure_time method to the instance using singleforwardable
|
161
165
|
#
|
162
|
-
def_delegators :instance, :definitions, :instantiate, :stringify, :round, :ceil, :floor, :currencies, :country_map, :defines?, :assert_currency!
|
166
|
+
def_delegators :instance, :definitions, :instantiate, :stringify, :symbol, :round, :ceil, :floor, :currencies, :country_map, :defines?, :assert_currency!
|
163
167
|
|
164
168
|
private
|
165
169
|
|
@@ -176,6 +180,9 @@ module Exchange
|
|
176
180
|
new_hsh
|
177
181
|
end
|
178
182
|
|
183
|
+
# Interpolates a string with separators every 3 characters
|
184
|
+
#
|
185
|
+
|
179
186
|
# get a precision for a specified amount and a specified currency
|
180
187
|
# @params [Float, Integer] amount The amount to get the precision for
|
181
188
|
# @params [Symbol] currency the currency to get the precision for
|
data/lib/exchange/money.rb
CHANGED
@@ -85,8 +85,8 @@ module Exchange
|
|
85
85
|
#
|
86
86
|
def to other, options={}
|
87
87
|
other = ISO.assert_currency!(other)
|
88
|
-
|
89
|
-
if api_supports_currency?(other)
|
88
|
+
|
89
|
+
if api_supports_currency?(other) && api_supports_currency?(currency)
|
90
90
|
opts = { :at => time, :from => self }.merge(options)
|
91
91
|
Money.new(api.new.convert(value, currency, other, opts), other, opts)
|
92
92
|
elsif fallback!
|
@@ -111,9 +111,12 @@ module Exchange
|
|
111
111
|
# @!macro [attach] install_operation
|
112
112
|
#
|
113
113
|
def install_operation op
|
114
|
-
define_method op do |*
|
115
|
-
psych
|
116
|
-
|
114
|
+
define_method op do |*arguments|
|
115
|
+
psych = arguments.first == :psych
|
116
|
+
precision = psych ? nil : arguments.first
|
117
|
+
val = ISO.send(op, self.value, self.currency, precision, {:psych => psych})
|
118
|
+
|
119
|
+
Exchange::Money.new(val, currency, :at => time, :from => self)
|
117
120
|
end
|
118
121
|
end
|
119
122
|
|
@@ -309,12 +312,18 @@ module Exchange
|
|
309
312
|
# @example Convert a currency with a three decimal minor to a string with a currency symbol
|
310
313
|
# Exchange::Money.new(34.34, :usd).to_s(:symbol) #=> "$34.34"
|
311
314
|
# @example Convert a currency with a three decimal minor to a string with just the amount
|
312
|
-
# Exchange::Money.new(
|
315
|
+
# Exchange::Money.new(3423.34, :usd).to_s(:amount) #=> "3,423.34"
|
316
|
+
# @example Convert a currency with just the plain amount using decimal notation
|
317
|
+
# Exchange::Money.new(3423.34, :omr).to_s(:plain) #=> "3423.340"
|
313
318
|
#
|
314
319
|
def to_s format=:currency
|
315
320
|
ISO.stringify(value, currency, :format => format)
|
316
321
|
end
|
317
322
|
|
323
|
+
# Returns the symbol for the given currency
|
324
|
+
# @since 1.0
|
325
|
+
# @version 0.1
|
326
|
+
|
318
327
|
private
|
319
328
|
|
320
329
|
# Fallback to the next api defined in the api fallbacks. Changes the api for the given instance
|
@@ -340,7 +349,7 @@ module Exchange
|
|
340
349
|
# @since 0.6
|
341
350
|
# @version 0.6
|
342
351
|
#
|
343
|
-
def
|
352
|
+
def is_money? other
|
344
353
|
other.is_a?(Exchange::Money)
|
345
354
|
end
|
346
355
|
|
@@ -351,7 +360,7 @@ module Exchange
|
|
351
360
|
# @version 0.6
|
352
361
|
#
|
353
362
|
def is_same_currency? other
|
354
|
-
|
363
|
+
is_money?(other) && other.currency == currency
|
355
364
|
end
|
356
365
|
|
357
366
|
# determine if another given object is an instance of Exchange::Money and has another currency
|
@@ -361,7 +370,7 @@ module Exchange
|
|
361
370
|
# @version 0.6
|
362
371
|
#
|
363
372
|
def is_other_currency? other
|
364
|
-
|
373
|
+
is_money?(other) && other.currency != currency
|
365
374
|
end
|
366
375
|
|
367
376
|
# determine wether the chosen api supports converting the given currency
|
@@ -379,7 +388,7 @@ module Exchange
|
|
379
388
|
# @version 0.6
|
380
389
|
#
|
381
390
|
def test_for_currency_mix_error other
|
382
|
-
raise ImplicitConversionError.new("You\'re trying to mix up #{currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Exchange.configuration.implicit_conversions &&
|
391
|
+
raise ImplicitConversionError.new("You\'re trying to mix up #{currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Exchange.configuration.implicit_conversions && is_other_currency?(other)
|
383
392
|
end
|
384
393
|
|
385
394
|
# Helper method to raise a no rate error for a given currency if no rate is given
|
@@ -389,13 +398,13 @@ module Exchange
|
|
389
398
|
# @version 0.7.2
|
390
399
|
#
|
391
400
|
def raise_no_rate_error other
|
392
|
-
raise NoRateError.new("Cannot convert to #{other} because the defined api nor the fallbacks provide a rate")
|
401
|
+
raise NoRateError.new("Cannot convert #{currency} to #{other} because the defined api nor the fallbacks provide a rate")
|
393
402
|
end
|
394
403
|
|
395
404
|
end
|
396
405
|
|
397
406
|
# The error that will get thrown when implicit conversions take place and are not allowed
|
398
407
|
#
|
399
|
-
ImplicitConversionError = Class.new
|
408
|
+
ImplicitConversionError = Class.new StandardError
|
400
409
|
|
401
410
|
end
|
data/spec/exchange/iso_spec.rb
CHANGED
@@ -781,6 +781,22 @@ describe "Exchange::ISO" do
|
|
781
781
|
subject.floor(BigDecimal.new("23.232524"), :clp, nil, {:psych => true}).should == BigDecimal.new("19")
|
782
782
|
end
|
783
783
|
end
|
784
|
+
describe "self.symbol" do
|
785
|
+
context "with a symbol present" do
|
786
|
+
it "should return the symbol" do
|
787
|
+
subject.symbol(:usd).should == '$'
|
788
|
+
subject.symbol(:gbp).should == '£'
|
789
|
+
subject.symbol(:eur).should == '€'
|
790
|
+
end
|
791
|
+
end
|
792
|
+
context "with no symbol present" do
|
793
|
+
it "should return nil" do
|
794
|
+
subject.symbol(:chf).should be_nil
|
795
|
+
subject.symbol(:etb).should be_nil
|
796
|
+
subject.symbol(:tnd).should be_nil
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
784
800
|
describe "self.stringify" do
|
785
801
|
it "should stringify a currency according to ISO 4217 Definitions" do
|
786
802
|
subject.stringify(BigDecimal.new("23234234.232524"), :tnd).should == "TND 23234234.233"
|
data/spec/exchange/money_spec.rb
CHANGED
@@ -75,14 +75,28 @@ describe "Exchange::Money" do
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
78
|
-
context "with a currency not provided by the given api" do
|
78
|
+
context "with a 'from' currency not provided by the given api" do
|
79
|
+
subject { Exchange::Money.new(36.36, :chf) }
|
80
|
+
context "but provided by a fallback api" do
|
81
|
+
it "should use the fallback" do
|
82
|
+
subject.api::CURRENCIES.should_receive(:include?).with(:usd).and_return true
|
83
|
+
subject.api::CURRENCIES.should_receive(:include?).with(:chf).and_return true
|
84
|
+
URI.should_receive(:parse).with("http://openexchangerates.org/api/latest.json?app_id=").once.and_raise Exchange::ExternalAPI::APIError
|
85
|
+
mock_api("http://api.finance.xaviermedia.com/api/#{Time.now.strftime("%Y/%m/%d")}.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
86
|
+
subject.to(:usd).value.round(2).should == 40.00
|
87
|
+
subject.to(:usd).currency.should == :usd
|
88
|
+
subject.to(:usd).should be_kind_of Exchange::Money
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
context "with a 'to' currency not provided by the given api" do
|
79
93
|
context "but provided by a fallback api" do
|
80
94
|
it "should use the fallback" do
|
81
95
|
subject.api::CURRENCIES.stub! :include? => false
|
82
96
|
mock_api("http://api.finance.xaviermedia.com/api/#{Time.now.strftime("%Y/%m/%d")}.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
83
|
-
subject.to(:
|
84
|
-
subject.to(:
|
85
|
-
subject.to(:
|
97
|
+
subject.to(:chf).value.round(2).should == 36.36
|
98
|
+
subject.to(:chf).currency.should == :chf
|
99
|
+
subject.to(:chf).should be_kind_of Exchange::Money
|
86
100
|
end
|
87
101
|
end
|
88
102
|
context "but not provided by any fallback api" do
|
metadata
CHANGED
@@ -1,81 +1,72 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exchange
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Beat Richartz
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: nokogiri
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 1.0.0
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: 1.0.0
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: json
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: 1.0.0
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: 1.0.0
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: yard
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: 0.7.4
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: 0.7.4
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: bundler
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: 1.0.0
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: 1.0.0
|
78
|
-
description:
|
69
|
+
description: 'Simple Money handling for your ruby app – ISO4217-compatible Currency
|
79
70
|
instantiation, conversion, string formatting and more via an intuitive DSL: 1.in(:usd).to(:eur)'
|
80
71
|
email: attr_accessor@gmail.com
|
81
72
|
executables: []
|
@@ -85,6 +76,8 @@ files:
|
|
85
76
|
- .document
|
86
77
|
- .gitignore
|
87
78
|
- .rspec
|
79
|
+
- .ruby-gemset
|
80
|
+
- .ruby-version
|
88
81
|
- .travis.yml
|
89
82
|
- Gemfile
|
90
83
|
- LICENSE.txt
|
@@ -165,27 +158,26 @@ files:
|
|
165
158
|
homepage: http://beatrichartz.github.com/exchange
|
166
159
|
licenses:
|
167
160
|
- MIT
|
161
|
+
metadata: {}
|
168
162
|
post_install_message:
|
169
163
|
rdoc_options: []
|
170
164
|
require_paths:
|
171
165
|
- lib
|
172
166
|
required_ruby_version: !ruby/object:Gem::Requirement
|
173
|
-
none: false
|
174
167
|
requirements:
|
175
|
-
- -
|
168
|
+
- - '>='
|
176
169
|
- !ruby/object:Gem::Version
|
177
170
|
version: '0'
|
178
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
|
-
none: false
|
180
172
|
requirements:
|
181
|
-
- -
|
173
|
+
- - '>='
|
182
174
|
- !ruby/object:Gem::Version
|
183
175
|
version: '0'
|
184
176
|
requirements: []
|
185
177
|
rubyforge_project:
|
186
|
-
rubygems_version:
|
178
|
+
rubygems_version: 2.0.3
|
187
179
|
signing_key:
|
188
|
-
specification_version:
|
180
|
+
specification_version: 4
|
189
181
|
summary: Simple Money handling for your ruby app
|
190
182
|
test_files:
|
191
183
|
- spec/exchange/cache/base_spec.rb
|