exchange 0.2.6

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.
Files changed (40) hide show
  1. data/.document +5 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +41 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +262 -0
  8. data/Rakefile +46 -0
  9. data/VERSION +1 -0
  10. data/exchange.gemspec +102 -0
  11. data/lib/core_extensions/conversability.rb +32 -0
  12. data/lib/exchange.rb +11 -0
  13. data/lib/exchange/cache.rb +4 -0
  14. data/lib/exchange/cache/base.rb +51 -0
  15. data/lib/exchange/cache/memcached.rb +52 -0
  16. data/lib/exchange/cache/rails.rb +45 -0
  17. data/lib/exchange/cache/redis.rb +54 -0
  18. data/lib/exchange/configuration.rb +72 -0
  19. data/lib/exchange/currency.rb +237 -0
  20. data/lib/exchange/external_api.rb +4 -0
  21. data/lib/exchange/external_api/base.rb +114 -0
  22. data/lib/exchange/external_api/call.rb +76 -0
  23. data/lib/exchange/external_api/currency_bot.rb +47 -0
  24. data/lib/exchange/external_api/xavier_media.rb +47 -0
  25. data/spec/core_extensions/conversability_spec.rb +64 -0
  26. data/spec/exchange/cache/base_spec.rb +29 -0
  27. data/spec/exchange/cache/memcached_spec.rb +72 -0
  28. data/spec/exchange/cache/rails_spec.rb +67 -0
  29. data/spec/exchange/cache/redis_spec.rb +76 -0
  30. data/spec/exchange/configuration_spec.rb +47 -0
  31. data/spec/exchange/currency_spec.rb +219 -0
  32. data/spec/exchange/external_api/base_spec.rb +31 -0
  33. data/spec/exchange/external_api/call_spec.rb +68 -0
  34. data/spec/exchange/external_api/currency_bot_spec.rb +61 -0
  35. data/spec/exchange/external_api/xavier_media_spec.rb +59 -0
  36. data/spec/spec_helper.rb +28 -0
  37. data/spec/support/api_responses/example_historic_json.json +167 -0
  38. data/spec/support/api_responses/example_json_api.json +167 -0
  39. data/spec/support/api_responses/example_xml_api.xml +156 -0
  40. metadata +191 -0
@@ -0,0 +1,32 @@
1
+ module Exchange
2
+
3
+ # The conversability module which will get included in Fixnum and Float, giving them the currency exchange methods
4
+ # @author Beat Richartz
5
+ # @version 0.2
6
+ # @since 0.1
7
+
8
+ module Conversability
9
+ # Method missing is used here to allow instantiation and immediate conversion of Currency objects from a common Fixnum or Float
10
+ # @example Instantiate from any type of number
11
+ # 40.usd => #<Exchange::Currency @value=40 @currency=:usd>
12
+ # -33.nok => #<Exchange::Currency @value=-33 @currency=:nok>
13
+ # 33.333.sek => #<Exchange::Currency @value=33.333 @currency=:sek>
14
+ # @example Instantiate and immediatly convert
15
+ # 1.usd.to_eur => #<Exchange::Currency @value=0.79 @currency=:eur>
16
+ # 1.nok.to_chf => #<Exchange::Currency @value=6.55 @currency=:chf>
17
+ # -3.5.dkk.to_huf => #<Exchange::Currency @value=-346.55 @currency=:huf>
18
+ # @example Instantiate and immediatly convert at a specific time in the past
19
+ # 1.usd.to_eur(:at => Time.now - 86400) => #<Exchange::Currency @value=0.80 @currency=:eur>
20
+ # 1.nok.to_chf(:at => Time.now - 3600) => #<Exchange::Currency @value=6.57 @currency=:chf>
21
+ # -3.5.dkk.to_huf(:at => Time.now - 172800) => #<Exchange::Currency @value=-337.40 @currency=:huf>
22
+
23
+ def method_missing method, *args, &block
24
+ return Exchange::Currency.new(self, method, *args) if method.to_s.length == 3 && Exchange::Configuration.api_class::CURRENCIES.include?(method.to_s)
25
+
26
+ super method, *args, &block
27
+ end
28
+ end
29
+ end
30
+
31
+ Fixnum.send :include, Exchange::Conversability
32
+ Float.send :include, Exchange::Conversability
@@ -0,0 +1,11 @@
1
+ require 'open-uri'
2
+ require 'bundler'
3
+ require 'json'
4
+ require 'nokogiri'
5
+ require 'redis'
6
+ require 'memcached'
7
+ require 'exchange/configuration'
8
+ require 'exchange/currency'
9
+ require 'exchange/external_api'
10
+ require 'exchange/cache'
11
+ require 'core_extensions/conversability'
@@ -0,0 +1,4 @@
1
+ require 'exchange/cache/base'
2
+ require 'exchange/cache/memcached'
3
+ require 'exchange/cache/redis'
4
+ require 'exchange/cache/rails'
@@ -0,0 +1,51 @@
1
+ module Exchange
2
+ # The cache module. All Classes handling caching for this gem have to be placed here. Allows easy extension with own caching solutions
3
+ # as shown in the example below.
4
+ # @example Write your own caching module
5
+ # module Cache
6
+ # class MyCustomCache < Base
7
+ # class << self
8
+ # # a cache class has to have the class method "cached"
9
+ # def cached api, opts={}, &block
10
+ # # generate the key with key(api, opts[:at]) and you will get a unique key to store in your cache
11
+ # # Your code goes here
12
+ # end
13
+ # end
14
+ # end
15
+ # end
16
+ # # Now, you can configure your Caching solution in the configuration. The Symbol will get camelcased and constantized
17
+ # Exchange::Configuration.cache = :my_custom_cache
18
+ # # Have fun, and don't forget to write tests.
19
+
20
+ module Cache
21
+
22
+ # The base Class for all Caching operations. Essentially generates a helper function for all cache classes to generate a key
23
+ # @author Beat Richartz
24
+ # @version 0.1
25
+ # @since 0.1
26
+
27
+ class Base
28
+ class << self
29
+
30
+ protected
31
+
32
+ # A Cache Key generator for the API Classes and the time
33
+ # Generates a key which can handle expiration by itself
34
+ # @param [Exchange::ExternalAPI::Subclass] api_class The API to store the data for
35
+ # @param [optional, Time] time The time for which the data is valid
36
+ # @return [String] A string that can be used as cache key
37
+ # @example
38
+ # Exchange::Cache::Base.key(Exchange::ExternalAPI::CurrencyBot, Time.gm(2012,1,1)) #=> "Exchange_ExternalAPI_CurrencyBot_2012_1"
39
+
40
+ def key(api_class, time=nil)
41
+ time ||= Time.now
42
+ key_parts = [api_class.to_s.gsub(/::/, '_'), time.year, time.yday]
43
+ key_parts << time.hour if Exchange::Configuration.update == :hourly
44
+ key_parts.join('_')
45
+ end
46
+
47
+ end
48
+ end
49
+ CachingWithoutBlockError = Class.new(ArgumentError)
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module Exchange
2
+ module Cache
3
+ # @author Beat Richartz
4
+ # A class that cooperates with the memcached gem to cache the data from the exchange api in memcached
5
+ #
6
+ # @version 0.1
7
+ # @since 0.1
8
+ # @example Activate caching via memcache by setting the cache in the configuration to :memcached
9
+ # Exchange::Configuration.define do |c|
10
+ # c.cache = :memcached
11
+ # c.cache_host = 'Your memcached host'
12
+ # c.cache_port = 'Your memcached port'
13
+ # end
14
+ class Memcached < Base
15
+ class << self
16
+
17
+ # instantiates a memcached client and memoizes it in a class variable.
18
+ # Use this client to access memcached data. For further explanation of use visit the memcached gem documentation
19
+ # @example
20
+ # Exchange::Cache::Memcached.client.set('FOO', 'BAR')
21
+ # @return [::Memcached] an instance of the memcached client gem class
22
+
23
+ def client
24
+ @@client ||= ::Memcached.new("#{Exchange::Configuration.cache_host}:#{Exchange::Configuration.cache_port}")
25
+ end
26
+
27
+ # returns either cached data from the memcached client or calls the block and caches it in memcached.
28
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
29
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
30
+ # @param [Hash] opts the options to cache with
31
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
32
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
33
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
34
+
35
+ def cached api, opts={}, &block
36
+ raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
37
+ begin
38
+ result = JSON.load client.get(key(api, opts[:at]))
39
+ rescue ::Memcached::NotFound
40
+ result = block.call
41
+ if result && !result.empty?
42
+ client.set key(api, opts[:at]), result.to_json, Exchange::Configuration.update == :daily ? 86400 : 3600
43
+ end
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ module Exchange
2
+ module Cache
3
+ # @author Beat Richartz
4
+ # A class that cooperates with rails to cache the data from the exchange api in rails
5
+ #
6
+ # @version 0.1
7
+ # @since 0.1
8
+ # @example Activate caching via rails by setting the cache in the configuration to :rails
9
+ # Exchange::Configuration.define do |c|
10
+ # c.cache = :rails
11
+ # end
12
+ class Rails < Base
13
+ class << self
14
+
15
+ # returns a Rails cache client. This has not to be stored since rails already memoizes it.
16
+ # Use this client to access rails cache data. For further explanation of use visit the rails documentation
17
+ # @example
18
+ # Exchange::Cache::Rails.client.set('FOO', 'BAR')
19
+ # @return [ActiveSupport::Cache::Subclass] an instance of the rails cache class (presumably a subclass of ActiveSupport::Cache)
20
+
21
+ def client
22
+ ::Rails.cache if defined?(::Rails)
23
+ end
24
+
25
+ # returns either cached data from the memcached client or calls the block and caches it in rails cache.
26
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
27
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
28
+ # @param [Hash] opts the options to cache with
29
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
30
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
31
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
32
+
33
+ def cached api, opts={}, &block
34
+ raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
35
+
36
+ result = client.fetch key(api, opts[:at]), :expires_in => Exchange::Configuration.update == :daily ? 86400 : 3600, &block
37
+ client.delete(key(api, opts[:at])) unless result && !result.empty?
38
+
39
+ result
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ module Exchange
2
+ module Cache
3
+ # @author Beat Richartz
4
+ # A class that cooperates with the redis gem and the redis key value store to cache the data from the exchange api in redis
5
+ #
6
+ # @version 0.1
7
+ # @since 0.1
8
+ # @example Activate caching via redis by setting the cache in the configuration to :redis
9
+ # Exchange::Configuration.define do |c|
10
+ # c.cache = :redis
11
+ # c.cache_host = 'Your redis host'
12
+ # c.cache_port = 'Your redis port (an Integer)'
13
+ # end
14
+ class Redis < Base
15
+ class << self
16
+
17
+ # instantiates a redis client and memoizes it in a class variable.
18
+ # Use this client to access redis data. For further explanation of use visit the redis gem documentation
19
+ # @example
20
+ # Exchange::Cache::Redis.client.set('FOO', 'BAR')
21
+ # @return [::Redis] an instance of the redis client gem class
22
+
23
+ def client
24
+ @@client ||= ::Redis.new(:host => Exchange::Configuration.cache_host, :port => Exchange::Configuration.cache_port)
25
+ end
26
+
27
+ # returns either cached data from the redis client or calls the block and caches it in redis.
28
+ # This method has to be the same in all the cache classes in order for the configuration binding to work
29
+ # @param [Exchange::ExternalAPI::Subclass] api The API class the data has to be stored for
30
+ # @param [Hash] opts the options to cache with
31
+ # @option opts [Time] :at the historic time of the exchange rates to be cached
32
+ # @yield [] This method takes a mandatory block with an arity of 0 and calls it if no cached result is available
33
+ # @raise [CachingWithoutBlockError] an Argument Error when no mandatory block has been given
34
+
35
+ def cached api, opts={}, &block
36
+ raise CachingWithoutBlockError.new('Caching needs a block') unless block_given?
37
+
38
+ if result = client.get(key(api, opts[:at]))
39
+ result = JSON.load result
40
+ else
41
+ result = block.call
42
+ if result && !result.empty?
43
+ client.set key(api, opts[:at]), result.to_json
44
+ client.expire key(api, opts[:at]), Exchange::Configuration.update == :daily ? 86400 : 3600
45
+ end
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,72 @@
1
+ module Exchange
2
+ # @author Beat Richartz
3
+ # A configuration class that stores the configuration of the gem. It allows to set the api from which the data gets retrieved,
4
+ # the cache in which the data gets cached, the regularity of updates for the currency rates, how many times the api calls should be
5
+ # retried on failure, and wether operations mixing currencies should raise errors or not
6
+ # @version 0.1
7
+ # @since 0.1
8
+ class Configuration
9
+ class << self
10
+ @@config ||= {:api => :currency_bot, :retries => 5, :allow_mixed_operations => true, :cache => :memcached, :cache_host => 'localhost', :cache_port => 11211, :update => :daily}
11
+
12
+ # A configuration method that stores the configuration of the gem. It allows to set the api from which the data gets retrieved,
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
14
+ # retried on failure, and wether operations mixing currencies should raise errors or not
15
+ # @version 0.1
16
+ # @since 0.1
17
+ # @example Set a configuration by passing a block to the configuration
18
+ # Exchange::Configuration.define do |c|
19
+ # c.cache = :redis
20
+ # c.cache_host = '127.0.0.1'
21
+ # c.cache_port = 6547
22
+ # c.api = :currency_bot
23
+ # c.retries = 1
24
+ # c.allow_mixed_operations = false
25
+ # c.update = :hourly
26
+ # end
27
+ # @yield [Exchange::Configuration] yields a the configuration class
28
+ # @yieldparam [optional, Symbol] cache The cache type to use. Possible Values: :redis, :memcached or :rails or false to disable caching. Defaults to :memcached
29
+ # @yieldparam [optional, String] cache_host A string with the hostname or IP to set the cache host to. Does not have to be set for Rails cache
30
+ # @yieldparam [optional, Integer] cache_port An integer for the cache port. Does not have to be set for Rails cache
31
+ # @yieldparam [optional, Symbol] api The API to use. Possible Values: :currency_bot (Open Source currency bot API) or :xavier_media (Xavier Media API). Defaults to :currency_bot
32
+ # @yieldparam [optional, Integer] retries The number of times the gem should retry to connect to the api host. Defaults to 5.
33
+ # @yieldparam [optional, Boolean] If set to false, Operations with with different currencies raise errors. Defaults to true.
34
+ # @yieldparam [optional, Symbol] The regularity of updates for the API. Possible values: :daily, :hourly. Defaults to :daily.
35
+ # @example Set configuration values directly to the class
36
+ # Exchange::Configuration.cache = :redis
37
+ # Exchange::Configuration.api = :xavier_media
38
+ def define &blk
39
+ self.instance_eval(&blk)
40
+ end
41
+
42
+ [:api, :retries, :cache, :cache_host, :cache_port, :update, :allow_mixed_operations].each do |m|
43
+ define_method m do
44
+ @@config[m]
45
+ end
46
+ define_method :"#{m}=" do |data|
47
+ @@config.merge! m => data
48
+ end
49
+ end
50
+
51
+ # The instantiated api class according to the configuration
52
+ # @example
53
+ # Exchange::Configuration.api = :currency_bot
54
+ # Exchange::Configuration.api_class #=> Exchange::ExternalAPI::CurrencyBot
55
+ # @return [Exchange::ExternalAPI::Subclass] A subclass of Exchange::ExternalAPI
56
+
57
+ def api_class
58
+ Exchange::ExternalAPI.const_get self.api.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
59
+ end
60
+
61
+ # The instantiated cache class according to the configuration
62
+ # @example
63
+ # Exchange::Configuration.cache = :redis
64
+ # Exchange::Configuration.cache_class #=> Exchange::ExternalAPI::Redis
65
+ # @return [Exchange::Cache::Subclass] A subclass of Exchange::Cache (or nil if caching has been set to false)
66
+
67
+ def cache_class
68
+ Exchange::Cache.const_get self.cache.to_s.gsub(/(?:^|_)(.)/) { $1.upcase } if self.cache
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,237 @@
1
+ # Top Level Module of the the gem.
2
+ # @author Beat Richartz
3
+ # @version 0.1
4
+ # @since 0.1
5
+
6
+ module Exchange
7
+ # @author Beat Richartz
8
+ # Currency Objects instantiated from the currency class can be used for basic mathematical operations and currency conversions
9
+ # @version 0.1
10
+ # @since 0.1
11
+ class Currency
12
+ include Comparable
13
+
14
+ # @attr_reader
15
+ # @return [Float] number The number the currency object has been instantiated from
16
+ attr_reader :value
17
+
18
+ # @attr_reader
19
+ # @return [Symbol, String] currency the currency of the currency object
20
+ attr_reader :currency
21
+
22
+ # @attr_reader
23
+ # @return [Time] The time at which the conversion has taken place or should take place if the object is involved in operations
24
+ attr_reader :time
25
+
26
+ # @attr_reader
27
+ # @return [Exchange::Currency] The original currency object this currency object was converted from
28
+ attr_reader :from
29
+
30
+ # Intialize the currency with a number and a currency
31
+ # @param [Integer, Float] number The number the currency is instantiated from
32
+ # @param [String, Symbol] currency The currency the currency object is in
33
+ # @param [Hash] opts Optional Parameters for instantiation
34
+ # @option opts [Time] :at The time at which conversion took place
35
+ # @option opts [String,Symbol] :from The currency object this currency object was converted from
36
+ # @version 0.2
37
+ #
38
+ # @example Instantiate a currency object of 40 US Dollars
39
+ # Exchange::Currency.new(40, :usd)
40
+ # #=> #<Exchange::Currency @number=40.0 @currency=:usd @time=#<Time>>
41
+ # @example Instantiate a currency object of 40 US Dollars and convert it to Euro. It shows the conversion date and the original currency
42
+ # Exchange::Currency.new(40, :usd).to_eur(:at => Time.gm(2012,9,1))
43
+ # #=> #<Exchange::Currency @number=37.0 @currency=:usd @time=#<Time> @from=#<Exchange::Currency @number=40.0 @currency=:usd>>
44
+
45
+ def initialize value, currency, opts={}
46
+ @value = value.to_f
47
+ @currency = currency
48
+ @time = assure_time(opts[:at], :default => :now)
49
+ @from = opts[:from] if opts[:from]
50
+ end
51
+
52
+ # Method missing is used to handle conversions from one currency object to another. It only handles currencies which are available in
53
+ # the API class set in the configuration.
54
+ # @example Calls convert_to with 'chf'
55
+ # Exchange::Currency.new(40,:usd).to_chf
56
+ # @example Calls convert_to with 'sek' and :at => Time.gm(2012,2,2)
57
+ # Exchange::Currency.new(40,:nok).to_sek(:at => Time.gm(2012,2,2))
58
+
59
+ def method_missing method, *args, &block
60
+ if method.to_s.match(/\Ato_(\w+)/) && Exchange::Configuration.api_class::CURRENCIES.include?($1)
61
+ args.first[:at] ||= time if args.first
62
+ return self.convert_to($1, args.first || {:at => self.time})
63
+ end
64
+
65
+ self.value.send method, *args, &block
66
+ end
67
+
68
+ # Converts this instance of currency into another currency
69
+ # @return [Exchange::Currency] An instance of Exchange::Currency with the converted number and the converted currency
70
+ # @param [Symbol, String] other The currency to convert the number to
71
+ # @param [Hash] opts An options hash
72
+ # @option [Time] :at The timestamp of the rate the conversion took place in
73
+ # @example convert to 'chf'
74
+ # Exchange::Currency.new(40,:usd).convert_to('chf')
75
+ # @example convert to 'sek' at a specific rate
76
+ # Exchange::Currency.new(40,:nok).convert_to('sek', :at => Time.gm(2012,2,2))
77
+
78
+ def convert_to other, opts={}
79
+ Exchange::Currency.new(Exchange::Configuration.api_class.new.convert(value, currency, other, opts), other, opts.merge(:from => self))
80
+ end
81
+
82
+ class << self
83
+
84
+ private
85
+ # @private
86
+ # @macro [attach] install_operations
87
+
88
+ def install_operation op
89
+ define_method op do
90
+ @value = self.value.send(op)
91
+ self
92
+ end
93
+ end
94
+
95
+ # @private
96
+ # @macro [attach] base_operations
97
+ # @method $1(other)
98
+
99
+ def base_operation op
100
+ self.class_eval <<-EOV
101
+ def #{op}(other)
102
+ #{'raise Exchange::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 !Exchange::Configuration.allow_mixed_operations && other.kind_of?(Exchange::Currency) && other.currency != self.currency'}
103
+ @value #{op}= other.kind_of?(Exchange::Currency) ? other.convert_to(self.currency, :at => other.time) : other
104
+ self
105
+ end
106
+ EOV
107
+ end
108
+
109
+ end
110
+
111
+ # Round the currency (Equivalent to normal round)
112
+ # @return [Exchange::Currency] The currency you started with with a rounded value
113
+ # @example
114
+ # Exchange::Currency.new(40.5, :usd).round
115
+ # #=> #<Exchange::Currency @number=41 @currency=:usd>
116
+
117
+ install_operation :round
118
+
119
+
120
+ # Ceil the currency (Equivalent to normal ceil)
121
+ # @return [Exchange::Currency] The currency you started with with a ceiled value
122
+ # @example
123
+ # Exchange::Currency.new(40.4, :usd).ceil
124
+ # #=> #<Exchange::Currency @number=41 @currency=:usd>
125
+
126
+ install_operation :ceil
127
+
128
+
129
+ # Floor the currency (Equivalent to normal floor)
130
+ # @return [Exchange::Currency] The currency you started with with a floored value
131
+ # @example
132
+ # Exchange::Currency.new(40.7, :usd).floor
133
+ # #=> #<Exchange::Currency @number=40 @currency=:usd>
134
+
135
+ install_operation :floor
136
+
137
+
138
+ # Add value to the currency
139
+ # @param [Integer, Float, Exchange::Currency] other The value to be added to the currency. If an Exchange::Currency, it is converted to the instance's currency and then the converted value is added.
140
+ # @return [Exchange::Currency] The currency with the added value
141
+ # @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
142
+ # @example Configuration disallows mixed operations
143
+ # Exchange::Configuration.allow_mixed_operations = false
144
+ # Exchange::Currency.new(20,:nok) + Exchange::Currency.new(20,:sek)
145
+ # #=> #<CurrencyMixError "You tried to mix currencies">
146
+ # @example Configuration allows mixed operations (default)
147
+ # Exchange::Currency.new(20,:nok) + Exchange::Currency.new(20,:sek)
148
+ # #=> #<Exchange::Currency @value=37.56 @currency=:nok>
149
+
150
+ base_operation '+'
151
+
152
+ # Subtract a value from the currency
153
+ # @param [Integer, Float, Exchange::Currency] other The value to be subtracted from the currency. If an Exchange::Currency, it is converted to the instance's currency and then subtracted from the converted value.
154
+ # @return [Exchange::Currency] The currency with the added value
155
+ # @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
156
+ # @example Configuration disallows mixed operations
157
+ # Exchange::Configuration.allow_mixed_operations = false
158
+ # Exchange::Currency.new(20,:nok) - Exchange::Currency.new(20,:sek)
159
+ # #=> #<CurrencyMixError "You tried to mix currencies">
160
+ # @example Configuration allows mixed operations (default)
161
+ # Exchange::Currency.new(20,:nok) - Exchange::Currency.new(20,:sek)
162
+ # #=> #<Exchange::Currency @value=7.56 @currency=:nok>
163
+
164
+ base_operation '-'
165
+
166
+ # Multiply a value with the currency
167
+ # @param [Integer, Float, Exchange::Currency] other The value to be multiplied with the currency. If an Exchange::Currency, it is converted to the instance's currency and multiplied with the converted value.
168
+ # @return [Exchange::Currency] The currency with the multiplied value
169
+ # @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
170
+ # @example Configuration disallows mixed operations
171
+ # Exchange::Configuration.allow_mixed_operations = false
172
+ # Exchange::Currency.new(20,:nok) * Exchange::Currency.new(20,:sek)
173
+ # #=> #<CurrencyMixError "You tried to mix currencies">
174
+ # @example Configuration allows mixed operations (default)
175
+ # Exchange::Currency.new(20,:nok) * Exchange::Currency.new(20,:sek)
176
+ # #=> #<Exchange::Currency @value=70.56 @currency=:nok>
177
+
178
+ base_operation '*'
179
+
180
+ # Divide the currency by a value
181
+ # @param [Integer, Float, Exchange::Currency] other The value to be divided by the currency. If an Exchange::Currency, it is converted to the instance's currency and divided by the converted value.
182
+ # @return [Exchange::Currency] The currency with the divided value
183
+ # @raise [CurrencyMixError] If the configuration does not allow mixed operations, this method will raise an error if two different currencies are used in the operation
184
+ # @example Configuration disallows mixed operations
185
+ # Exchange::Configuration.allow_mixed_operations = false
186
+ # Exchange::Currency.new(20,:nok) / Exchange::Currency.new(20,:sek)
187
+ # #=> #<CurrencyMixError "You tried to mix currencies">
188
+ # @example Configuration allows mixed operations (default)
189
+ # Exchange::Currency.new(20,:nok) / Exchange::Currency.new(20,:sek)
190
+ # #=> #<Exchange::Currency @value=1.56 @currency=:nok>
191
+
192
+ base_operation '/'
193
+
194
+ def == other
195
+ if other.is_a?(Exchange::Currency) && other.currency == self.currency
196
+ other.value == self.value
197
+ elsif other.is_a?(Exchange::Currency)
198
+ other.convert_to(self.currency, :at => other.time).value == self.value
199
+ else
200
+ self.value == other
201
+ end
202
+ end
203
+
204
+ def <=> other
205
+ 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
+ -1
207
+ 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))
208
+ 1
209
+ elsif other.is_a?(Exchange::Currency)
210
+ 0
211
+ else
212
+ self.value <=> other
213
+ end
214
+
215
+ end
216
+
217
+ protected
218
+
219
+ # A helper function to assure a value is an instance of time
220
+ # @param [Time, String, NilClass] The value to be asserted
221
+ # @param [Hash] opts Options for assertion
222
+ # @option opts [Symbol] :default If the argument is nil, you can define :default as :now to be delivered with Time.now instead of nil
223
+ # @version 0.2
224
+
225
+ def assure_time(arg=nil, opts={})
226
+ if arg
227
+ arg.kind_of?(Time) ? arg : Time.gm(*arg.split('-'))
228
+ elsif opts[:default] == :now
229
+ Time.now
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ # The error that will get thrown when currencies get mixed up in base operations
236
+ CurrencyMixError = Class.new(ArgumentError)
237
+ end