exchange 0.2.6 → 0.3.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/.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
@@ -6,7 +6,10 @@ module Exchange
|
|
6
6
|
# @since 0.1
|
7
7
|
|
8
8
|
module Conversability
|
9
|
-
#
|
9
|
+
# Dynamic method generation is used here to allow instantiation and immediate conversion of Currency objects from
|
10
|
+
# a common Fixnum or Float or BigDecimal. Since ruby 1.9 handles certain type conversion of Fixnum, Float and others
|
11
|
+
# via method missing, this is not handled via method missing because it would seriously break down performance.
|
12
|
+
#
|
10
13
|
# @example Instantiate from any type of number
|
11
14
|
# 40.usd => #<Exchange::Currency @value=40 @currency=:usd>
|
12
15
|
# -33.nok => #<Exchange::Currency @value=-33 @currency=:nok>
|
@@ -20,13 +23,15 @@ module Exchange
|
|
20
23
|
# 1.nok.to_chf(:at => Time.now - 3600) => #<Exchange::Currency @value=6.57 @currency=:chf>
|
21
24
|
# -3.5.dkk.to_huf(:at => Time.now - 172800) => #<Exchange::Currency @value=-337.40 @currency=:huf>
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
ISO4217.definitions.keys.each do |c|
|
27
|
+
define_method c.downcase.to_sym do |*args|
|
28
|
+
Currency.new(self, c, *args)
|
29
|
+
end
|
27
30
|
end
|
31
|
+
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
31
|
-
Fixnum.send
|
32
|
-
Float.send
|
35
|
+
Fixnum.send :include, Exchange::Conversability
|
36
|
+
Float.send :include, Exchange::Conversability
|
37
|
+
BigDecimal.send :include, Exchange::Conversability
|
data/lib/exchange.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
+
require 'bigdecimal'
|
1
2
|
require 'open-uri'
|
2
3
|
require 'bundler'
|
3
4
|
require 'json'
|
4
5
|
require 'nokogiri'
|
5
6
|
require 'redis'
|
6
7
|
require 'memcached'
|
8
|
+
require 'exchange/helper'
|
7
9
|
require 'exchange/configuration'
|
10
|
+
require 'exchange/iso_4217'
|
8
11
|
require 'exchange/currency'
|
9
12
|
require 'exchange/external_api'
|
10
13
|
require 'exchange/cache'
|
11
|
-
require 'core_extensions/conversability'
|
14
|
+
require 'core_extensions/conversability'
|
15
|
+
|
16
|
+
# The error that gets thrown if no conversion rate is available
|
17
|
+
NoRateError = Class.new(StandardError)
|
data/lib/exchange/cache.rb
CHANGED
data/lib/exchange/cache/base.rb
CHANGED
@@ -37,11 +37,15 @@ module Exchange
|
|
37
37
|
# @example
|
38
38
|
# Exchange::Cache::Base.key(Exchange::ExternalAPI::CurrencyBot, Time.gm(2012,1,1)) #=> "Exchange_ExternalAPI_CurrencyBot_2012_1"
|
39
39
|
|
40
|
-
def key(
|
41
|
-
time
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
def key(api, opts={})
|
41
|
+
time = Exchange::Helper.assure_time(opts[:at], :default => :now)
|
42
|
+
[ 'exchange',
|
43
|
+
api.to_s,
|
44
|
+
time.year.to_s,
|
45
|
+
time.yday.to_s,
|
46
|
+
Exchange::Configuration.update == :hourly ? time.hour.to_s : nil,
|
47
|
+
*(opts[:key_for] || [])
|
48
|
+
].compact.join('_')
|
45
49
|
end
|
46
50
|
|
47
51
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Exchange
|
2
|
+
module Cache
|
3
|
+
|
4
|
+
# @author Beat Richartz
|
5
|
+
# A class that allows to store api call results in files. THIS NOT A RECOMMENDED CACHING OPTION!
|
6
|
+
# It just may be necessary to cache large files somewhere, this class allows you to do that
|
7
|
+
#
|
8
|
+
# @version 0.3
|
9
|
+
# @since 0.3
|
10
|
+
|
11
|
+
class File < Base
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# returns either cached data from a stored file or stores a file.
|
15
|
+
# This method has to be the same in all the cache classes in order for the configuration binding to work
|
16
|
+
# @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
|
17
|
+
# @param [Hash] opts the options to cache with
|
18
|
+
# @option opts [Time] :at IS IGNORED FOR FILECACHE
|
19
|
+
# @option opts [Symbol] :cache_period The period to cache the file for
|
20
|
+
# @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
|
21
|
+
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
22
|
+
|
23
|
+
def cached api, opts={}, &block
|
24
|
+
raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
|
25
|
+
|
26
|
+
today = Time.now
|
27
|
+
dir = Exchange::Configuration.filestore_path
|
28
|
+
path = ::File.join(dir, key(api, opts[:cache_period]))
|
29
|
+
|
30
|
+
if ::File.exists?(path)
|
31
|
+
result = ::File.read(path)
|
32
|
+
else
|
33
|
+
result = block.call
|
34
|
+
if result && !result.to_s.empty?
|
35
|
+
FileUtils.mkdir_p(dir) unless Dir.respond_to?(:exists?) && Dir.exists?(dir)
|
36
|
+
keep_files = [key(api, :daily), key(api, :monthly)]
|
37
|
+
Dir.entries(dir).each do |e|
|
38
|
+
::File.delete(::File.join(dir, e)) unless keep_files.include?(e) || e.match(/\A\./)
|
39
|
+
end
|
40
|
+
::File.open(path, 'w') {|f| f.write(result) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# A Cache Key generator for the file Cache Class and the time
|
50
|
+
# Generates a key which can handle expiration by itself
|
51
|
+
# @param [Exchange::ExternalAPI::Subclass] api_class The API to store the data for
|
52
|
+
# @param [optional, Symbol] cache_period The time for which the data is valid
|
53
|
+
# @return [String] A string that can be used as cache key
|
54
|
+
# @example
|
55
|
+
# Exchange::Cache::Base.key(Exchange::ExternalAPI::CurrencyBot, :monthly) #=> "Exchange_ExternalAPI_CurrencyBot_monthly_2012_1"
|
56
|
+
|
57
|
+
def key(api_class, cache_period=:daily)
|
58
|
+
time = Time.now
|
59
|
+
[api_class.to_s.gsub(/::/, '_'), cache_period, time.year, time.send(cache_period == :monthly ? :month : :yday)].join('_')
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -21,7 +21,7 @@ module Exchange
|
|
21
21
|
# @return [::Memcached] an instance of the memcached client gem class
|
22
22
|
|
23
23
|
def client
|
24
|
-
@@client ||= ::Memcached.new("#{
|
24
|
+
@@client ||= ::Memcached.new("#{Configuration.cache_host}:#{Configuration.cache_port}")
|
25
25
|
end
|
26
26
|
|
27
27
|
# returns either cached data from the memcached client or calls the block and caches it in memcached.
|
@@ -35,11 +35,11 @@ module Exchange
|
|
35
35
|
def cached api, opts={}, &block
|
36
36
|
raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
|
37
37
|
begin
|
38
|
-
result = JSON.load client.get(key(api, opts
|
38
|
+
result = JSON.load client.get(key(api, opts))
|
39
39
|
rescue ::Memcached::NotFound
|
40
40
|
result = block.call
|
41
|
-
if result && !result.empty?
|
42
|
-
client.set key(api, opts
|
41
|
+
if result && !result.to_s.empty?
|
42
|
+
client.set key(api, opts), result.to_json, Configuration.update == :daily ? 86400 : 3600
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Exchange
|
2
|
+
module Cache
|
3
|
+
|
4
|
+
# @author Beat Richartz
|
5
|
+
# A class that allows to store api call results in files. THIS NOT A RECOMMENDED CACHING OPTION!
|
6
|
+
# It just may be necessary to cache large files somewhere, this class allows you to do that
|
7
|
+
#
|
8
|
+
# @version 0.3
|
9
|
+
# @since 0.3
|
10
|
+
|
11
|
+
class NoCache < Base
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# returns either cached data from a stored file or stores a file.
|
15
|
+
# This method has to be the same in all the cache classes in order for the configuration binding to work
|
16
|
+
# @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
|
17
|
+
# @param [Hash] opts the options to cache with
|
18
|
+
# @option opts [Time] :at IS IGNORED FOR FILECACHE
|
19
|
+
# @option opts [Symbol] :cache_period The period to cache the file for
|
20
|
+
# @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
|
21
|
+
# @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
|
22
|
+
|
23
|
+
def cached api, opts={}, &block
|
24
|
+
raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
|
25
|
+
|
26
|
+
block.call
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/exchange/cache/rails.rb
CHANGED
@@ -33,8 +33,8 @@ module Exchange
|
|
33
33
|
def cached api, opts={}, &block
|
34
34
|
raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
|
35
35
|
|
36
|
-
result = client.fetch key(api, opts
|
37
|
-
client.delete(key(api, opts
|
36
|
+
result = client.fetch key(api, opts), :expires_in => Configuration.update == :daily ? 86400 : 3600, &block
|
37
|
+
client.delete(key(api, opts)) unless result && !result.to_s.empty?
|
38
38
|
|
39
39
|
result
|
40
40
|
end
|
data/lib/exchange/cache/redis.rb
CHANGED
@@ -21,7 +21,7 @@ module Exchange
|
|
21
21
|
# @return [::Redis] an instance of the redis client gem class
|
22
22
|
|
23
23
|
def client
|
24
|
-
@@client ||= ::Redis.new(:host =>
|
24
|
+
@@client ||= ::Redis.new(:host => Configuration.cache_host, :port => Configuration.cache_port)
|
25
25
|
end
|
26
26
|
|
27
27
|
# returns either cached data from the redis client or calls the block and caches it in redis.
|
@@ -35,13 +35,13 @@ module Exchange
|
|
35
35
|
def cached api, opts={}, &block
|
36
36
|
raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
|
37
37
|
|
38
|
-
if result = client.get(key(api, opts
|
38
|
+
if result = client.get(key(api, opts))
|
39
39
|
result = JSON.load result
|
40
40
|
else
|
41
41
|
result = block.call
|
42
|
-
if result && !result.empty?
|
43
|
-
client.set key(api, opts
|
44
|
-
client.expire key(api, opts
|
42
|
+
if result && !result.to_s.empty?
|
43
|
+
client.set key(api, opts), result.to_json
|
44
|
+
client.expire key(api, opts), Configuration.update == :daily ? 86400 : 3600
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -7,7 +7,7 @@ module Exchange
|
|
7
7
|
# @since 0.1
|
8
8
|
class Configuration
|
9
9
|
class << self
|
10
|
-
@@config ||= {:api => :currency_bot, :retries => 5, :allow_mixed_operations => true, :cache => :memcached, :cache_host => 'localhost', :cache_port => 11211, :update => :daily}
|
10
|
+
@@config ||= {:api => :currency_bot, :retries => 5, :filestore_path => File.expand_path('exchange_filestore'), :allow_mixed_operations => true, :cache => :memcached, :cache_host => 'localhost', :cache_port => 11211, :update => :daily}
|
11
11
|
|
12
12
|
# A configuration method that stores the configuration of the gem. It allows to set the api from which the data gets retrieved,
|
13
13
|
# the cache in which the data gets cached, the regularity of updates for the currency rates, how many times the api calls should be
|
@@ -32,14 +32,16 @@ module Exchange
|
|
32
32
|
# @yieldparam [optional, Integer] retries The number of times the gem should retry to connect to the api host. Defaults to 5.
|
33
33
|
# @yieldparam [optional, Boolean] If set to false, Operations with with different currencies raise errors. Defaults to true.
|
34
34
|
# @yieldparam [optional, Symbol] The regularity of updates for the API. Possible values: :daily, :hourly. Defaults to :daily.
|
35
|
+
# @yieldparam [optional, String] The path where files can be stored for the gem (used for large files from ECB). Make sure ruby has write access.
|
35
36
|
# @example Set configuration values directly to the class
|
36
37
|
# Exchange::Configuration.cache = :redis
|
37
38
|
# Exchange::Configuration.api = :xavier_media
|
39
|
+
|
38
40
|
def define &blk
|
39
41
|
self.instance_eval(&blk)
|
40
42
|
end
|
41
|
-
|
42
|
-
[:api, :retries, :cache, :cache_host, :cache_port, :update, :allow_mixed_operations].each do |m|
|
43
|
+
|
44
|
+
[:api, :retries, :cache, :cache_host, :cache_port, :filestore_path, :update, :allow_mixed_operations].each do |m|
|
43
45
|
define_method m do
|
44
46
|
@@config[m]
|
45
47
|
end
|
@@ -52,20 +54,34 @@ module Exchange
|
|
52
54
|
# @example
|
53
55
|
# Exchange::Configuration.api = :currency_bot
|
54
56
|
# Exchange::Configuration.api_class #=> Exchange::ExternalAPI::CurrencyBot
|
57
|
+
# @param [Hash] options A hash of Options
|
58
|
+
# @option options [Class] :api A api to return instead of the api class (use for fallback)
|
55
59
|
# @return [Exchange::ExternalAPI::Subclass] A subclass of Exchange::ExternalAPI
|
56
60
|
|
57
|
-
def api_class
|
58
|
-
|
61
|
+
def api_class(options={})
|
62
|
+
@api_class ||= {}
|
63
|
+
return @api_class[options[:api] || self.api] if @api_class[options[:api] || self.api]
|
64
|
+
|
65
|
+
@api_class[options[:api] || self.api] = ExternalAPI.const_get((options[:api] || self.api).to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
|
59
66
|
end
|
60
67
|
|
61
68
|
# The instantiated cache class according to the configuration
|
62
69
|
# @example
|
63
70
|
# Exchange::Configuration.cache = :redis
|
64
71
|
# Exchange::Configuration.cache_class #=> Exchange::ExternalAPI::Redis
|
72
|
+
# @param [Hash] options A hash of Options
|
73
|
+
# @option options [Class] :api A api to return instead of the api class (use for fallback)
|
65
74
|
# @return [Exchange::Cache::Subclass] A subclass of Exchange::Cache (or nil if caching has been set to false)
|
66
75
|
|
67
|
-
def cache_class
|
68
|
-
|
76
|
+
def cache_class(options={})
|
77
|
+
@cache_class ||= {}
|
78
|
+
return @cache_class[options[:cache] || self.cache] if @cache_class[options[:cache] || self.cache]
|
79
|
+
|
80
|
+
@cache_class[options[:cache] || self.cache] = if self.cache
|
81
|
+
Cache.const_get((options[:cache] || self.cache).to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
|
82
|
+
else
|
83
|
+
Cache::NoCache
|
84
|
+
end
|
69
85
|
end
|
70
86
|
end
|
71
87
|
end
|
data/lib/exchange/currency.rb
CHANGED
@@ -43,10 +43,10 @@ module Exchange
|
|
43
43
|
# #=> #<Exchange::Currency @number=37.0 @currency=:usd @time=#<Time> @from=#<Exchange::Currency @number=40.0 @currency=:usd>>
|
44
44
|
|
45
45
|
def initialize value, currency, opts={}
|
46
|
-
@value = value
|
46
|
+
@value = ISO4217.instantiate(value, currency)
|
47
47
|
@currency = currency
|
48
|
-
@time = assure_time(opts[:at], :default => :now)
|
49
|
-
@from = opts[:from]
|
48
|
+
@time = Helper.assure_time(opts[:at], :default => :now)
|
49
|
+
@from = opts[:from]
|
50
50
|
end
|
51
51
|
|
52
52
|
# Method missing is used to handle conversions from one currency object to another. It only handles currencies which are available in
|
@@ -57,9 +57,11 @@ module Exchange
|
|
57
57
|
# Exchange::Currency.new(40,:nok).to_sek(:at => Time.gm(2012,2,2))
|
58
58
|
|
59
59
|
def method_missing method, *args, &block
|
60
|
-
|
61
|
-
|
62
|
-
return self.convert_to($1,
|
60
|
+
match = method.to_s.match(/\Ato_(\w{3})/)
|
61
|
+
if match && Configuration.api_class::CURRENCIES.include?($1)
|
62
|
+
return self.convert_to($1, {:at => self.time}.merge(args.first || {}))
|
63
|
+
elsif match && ISO4217.definitions.keys.include?($1.upcase)
|
64
|
+
raise NoRateError.new("Cannot convert to #{$1} because the defined api does not provide a rate")
|
63
65
|
end
|
64
66
|
|
65
67
|
self.value.send method, *args, &block
|
@@ -76,7 +78,7 @@ module Exchange
|
|
76
78
|
# Exchange::Currency.new(40,:nok).convert_to('sek', :at => Time.gm(2012,2,2))
|
77
79
|
|
78
80
|
def convert_to other, opts={}
|
79
|
-
|
81
|
+
Currency.new(Configuration.api_class.new.convert(value, currency, other, opts), other, opts.merge(:from => self))
|
80
82
|
end
|
81
83
|
|
82
84
|
class << self
|
@@ -86,8 +88,8 @@ module Exchange
|
|
86
88
|
# @macro [attach] install_operations
|
87
89
|
|
88
90
|
def install_operation op
|
89
|
-
define_method op do
|
90
|
-
@value = self.value.
|
91
|
+
define_method op do |*precision|
|
92
|
+
@value = ISO4217.send(op, self.value, self.currency, precision.first)
|
91
93
|
self
|
92
94
|
end
|
93
95
|
end
|
@@ -99,8 +101,8 @@ module Exchange
|
|
99
101
|
def base_operation op
|
100
102
|
self.class_eval <<-EOV
|
101
103
|
def #{op}(other)
|
102
|
-
#{'raise
|
103
|
-
@value #{op}= other.kind_of?(
|
104
|
+
#{'raise CurrencyMixError.new("You\'re trying to mix up #{self.currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Configuration.allow_mixed_operations && other.kind_of?(Currency) && other.currency != self.currency'}
|
105
|
+
@value #{op}= other.kind_of?(Currency) ? other.convert_to(self.currency, :at => other.time) : other
|
104
106
|
self
|
105
107
|
end
|
106
108
|
EOV
|
@@ -108,29 +110,50 @@ module Exchange
|
|
108
110
|
|
109
111
|
end
|
110
112
|
|
111
|
-
# Round the currency
|
113
|
+
# Round the currency. Since this is a currency, it will round to the standard decimal value.
|
114
|
+
# If you want to round it to another precision, you have to specifically ask for it.
|
112
115
|
# @return [Exchange::Currency] The currency you started with with a rounded value
|
113
|
-
# @
|
114
|
-
#
|
115
|
-
#
|
116
|
+
# @param [Integer] precision The precision you want the rounding to have. Defaults to the ISO 4217 standard value for the currency
|
117
|
+
# @since 0.1
|
118
|
+
# @version 0.3
|
119
|
+
# @example Round your currency to the iso standard number of decimals
|
120
|
+
# Exchange::Currency.new(40.545, :usd).round
|
121
|
+
# #=> #<Exchange::Currency @value=40.55 @currency=:usd>
|
122
|
+
# @example Round your currency to another number of decimals
|
123
|
+
# Exchange::Currency.new(40.545, :usd).round(0)
|
124
|
+
# #=> #<Exchange::Currency @value=41 @currency=:usd>
|
116
125
|
|
117
126
|
install_operation :round
|
118
127
|
|
119
128
|
|
120
|
-
# Ceil the currency
|
129
|
+
# Ceil the currency. Since this is a currency, it will ceil to the standard decimal value.
|
130
|
+
# If you want to ceil it to another precision, you have to specifically ask for it.
|
121
131
|
# @return [Exchange::Currency] The currency you started with with a ceiled value
|
122
|
-
# @
|
123
|
-
#
|
124
|
-
#
|
132
|
+
# @param [Integer] precision The precision you want the ceiling to have. Defaults to the ISO 4217 standard value for the currency
|
133
|
+
# @since 0.1
|
134
|
+
# @version 0.3
|
135
|
+
# @example Ceil your currency to the iso standard number of decimals
|
136
|
+
# Exchange::Currency.new(40.544, :usd).ceil
|
137
|
+
# #=> #<Exchange::Currency @value=40.55 @currency=:usd>
|
138
|
+
# @example Ceil your currency to another number of decimals
|
139
|
+
# Exchange::Currency.new(40.445, :usd).ceil(0)
|
140
|
+
# #=> #<Exchange::Currency @value=41 @currency=:usd>
|
125
141
|
|
126
142
|
install_operation :ceil
|
127
143
|
|
128
144
|
|
129
|
-
# Floor the currency
|
145
|
+
# Floor the currency. Since this is a currency, it will ceil to the standard decimal value.
|
146
|
+
# If you want to ceil it to another precision, you have to specifically ask for it.
|
130
147
|
# @return [Exchange::Currency] The currency you started with with a floored value
|
131
|
-
# @
|
132
|
-
#
|
133
|
-
#
|
148
|
+
# @param [Integer] precision The precision you want the flooring to have. Defaults to the ISO 4217 standard value for the currency
|
149
|
+
# @since 0.1
|
150
|
+
# @version 0.3
|
151
|
+
# @example Floor your currency to the iso standard number of decimals
|
152
|
+
# Exchange::Currency.new(40.545, :usd).floor
|
153
|
+
# #=> #<Exchange::Currency @value=40.54 @currency=:usd>
|
154
|
+
# @example Floor your currency to another number of decimals
|
155
|
+
# Exchange::Currency.new(40.545, :usd).floor(0)
|
156
|
+
# #=> #<Exchange::Currency @value=40 @currency=:usd>
|
134
157
|
|
135
158
|
install_operation :floor
|
136
159
|
|
@@ -191,17 +214,42 @@ module Exchange
|
|
191
214
|
|
192
215
|
base_operation '/'
|
193
216
|
|
217
|
+
# Compare a currency with another currency or another value. If the other is not an instance of Exchange::Currency, the value
|
218
|
+
# of the currency is compared
|
219
|
+
# @param [Whatever you want to throw at it] other The counterpart to compare
|
220
|
+
# @return [Boolean] true if the other is equal, false if not
|
221
|
+
# @example Compare two currencies
|
222
|
+
# Exchange::Currency.new(40, :usd) == Exchange::Currency.new(34, :usd) #=> true
|
223
|
+
# @example Compare two different currencies, the other will get converted for comparison
|
224
|
+
# Exchange::Currency.new(40, :usd) == Exchange::Currency.new(34, :eur) #=> true, will implicitly convert eur to usd at the actual rate
|
225
|
+
# @example Compare a currency with a number, the value of the currency will get compared
|
226
|
+
# Exchange::Currency.new(35, :usd) == 35 #=> true
|
227
|
+
|
194
228
|
def == other
|
195
229
|
if other.is_a?(Exchange::Currency) && other.currency == self.currency
|
196
|
-
other.value == self.value
|
230
|
+
other.round.value == self.round.value
|
197
231
|
elsif other.is_a?(Exchange::Currency)
|
198
|
-
other.convert_to(self.currency, :at => other.time).value == self.value
|
232
|
+
other.convert_to(self.currency, :at => other.time).round.value == self.round.value
|
199
233
|
else
|
200
234
|
self.value == other
|
201
235
|
end
|
202
236
|
end
|
203
237
|
|
238
|
+
# Sortcompare a currency with another currency. If the other is not an instance of Exchange::Currency, the value
|
239
|
+
# of the currency is compared. Different currencies will be converted to the comparing instances currency
|
240
|
+
# @param [Whatever you want to throw at it] other The counterpart to compare
|
241
|
+
# @return [Fixed] a number which can be used for sorting
|
242
|
+
# @since 0.3
|
243
|
+
# @version 0.3
|
244
|
+
# @example Compare two currencies in terms of value
|
245
|
+
# Exchange::Currency.new(40, :usd) <=> Exchange::Currency.new(28, :usd) #=> -1
|
246
|
+
# @example Compare two different currencies, the other will get converted for comparison
|
247
|
+
# Exchange::Currency.new(40, :usd) <=> Exchange::Currency.new(28, :eur) #=> -1
|
248
|
+
# @example Sort multiple currencies in an array
|
249
|
+
# [1.usd, 1.eur, 1.chf].sort.map(&:currency) #=> [:usd, :chf, :eur]
|
250
|
+
|
204
251
|
def <=> other
|
252
|
+
# TODO which historic conversion should be used when two are present?
|
205
253
|
if other.is_a?(Exchange::Currency) && ((other.currency == self.currency && self.value < other.value) || (other.currency != self.currency && self.value < other.convert_to(self.currency, :at => other.time).value))
|
206
254
|
-1
|
207
255
|
elsif other.is_a?(Exchange::Currency) && ((other.currency == self.currency && self.value > other.value) || (other.currency != self.currency && self.value > other.convert_to(self.currency, :at => other.time).value))
|
@@ -214,21 +262,27 @@ module Exchange
|
|
214
262
|
|
215
263
|
end
|
216
264
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
265
|
+
# Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
|
266
|
+
# with no worries how to display the currency.
|
267
|
+
# @since 0.3
|
268
|
+
# @version 0.3
|
269
|
+
# @param [Symbol] format :currency (default) if you want a string with currency, :amount if you want just the amount.
|
270
|
+
# @return [String] The formatted string
|
271
|
+
# @example Convert a currency to a string
|
272
|
+
# Exchange::Currency.new(49.567, :usd).to_s #=> "USD 49.57"
|
273
|
+
# @example Convert a currency without minor to a string
|
274
|
+
# Exchange::Currency.new(45, :jpy).to_s #=> "JPY 45"
|
275
|
+
# @example Convert a currency with a three decimal minor to a string
|
276
|
+
# Exchange::Currency.new(34.34, :omr).to_s #=> "OMR 34.340"
|
277
|
+
# @example Convert a currency to a string without the currency
|
278
|
+
# Exchange::ISO4217.stringif(34.34, :omr).to_s(:iso) #=> "34.340"
|
279
|
+
|
280
|
+
def to_s format=:currency
|
281
|
+
[
|
282
|
+
format == :currency && ISO4217.stringify(self.value, self.currency),
|
283
|
+
format == :amount && ISO4217.stringify(self.value, self.currency, :amount_only => true)
|
284
|
+
].detect{|l| l.is_a?(String) }
|
285
|
+
end
|
232
286
|
|
233
287
|
end
|
234
288
|
|