exchange 1.0.4 → 1.1.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.
- 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
|