exchange 0.12.0 → 1.0.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.
Files changed (69) hide show
  1. data/.travis.yml +1 -0
  2. data/README.rdoc +34 -6
  3. data/Rakefile +3 -30
  4. data/changelog.rdoc +6 -0
  5. data/exchange.gemspec +3 -6
  6. data/iso4217.yml +238 -81
  7. data/lib/exchange.rb +2 -1
  8. data/lib/exchange/base.rb +3 -2
  9. data/lib/exchange/cache.rb +1 -0
  10. data/lib/exchange/cache/base.rb +2 -1
  11. data/lib/exchange/cache/configuration.rb +2 -1
  12. data/lib/exchange/cache/file.rb +2 -1
  13. data/lib/exchange/cache/memcached.rb +2 -1
  14. data/lib/exchange/cache/memory.rb +2 -1
  15. data/lib/exchange/cache/no_cache.rb +2 -1
  16. data/lib/exchange/cache/rails.rb +2 -1
  17. data/lib/exchange/cache/redis.rb +2 -1
  18. data/lib/exchange/configurable.rb +3 -2
  19. data/lib/exchange/configuration.rb +8 -5
  20. data/lib/exchange/core_extensions.rb +2 -1
  21. data/lib/exchange/core_extensions/cachify.rb +2 -1
  22. data/lib/exchange/core_extensions/float/error_safe.rb +2 -1
  23. data/lib/exchange/core_extensions/numeric/conversability.rb +2 -1
  24. data/lib/exchange/external_api.rb +3 -1
  25. data/lib/exchange/external_api/base.rb +2 -1
  26. data/lib/exchange/external_api/call.rb +2 -1
  27. data/lib/exchange/external_api/configuration.rb +18 -3
  28. data/lib/exchange/external_api/ecb.rb +2 -1
  29. data/lib/exchange/external_api/json.rb +2 -1
  30. data/lib/exchange/external_api/open_exchange_rates.rb +2 -1
  31. data/lib/exchange/external_api/random.rb +31 -0
  32. data/lib/exchange/external_api/xavier_media.rb +2 -1
  33. data/lib/exchange/external_api/xml.rb +2 -1
  34. data/lib/exchange/gem_loader.rb +2 -1
  35. data/lib/exchange/helper.rb +2 -1
  36. data/lib/exchange/iso.rb +29 -13
  37. data/lib/exchange/money.rb +35 -9
  38. data/lib/exchange/typecasting.rb +2 -1
  39. data/spec/exchange/cache/base_spec.rb +2 -1
  40. data/spec/exchange/cache/configuration_spec.rb +4 -1
  41. data/spec/exchange/cache/file_spec.rb +2 -1
  42. data/spec/exchange/cache/memcached_spec.rb +2 -1
  43. data/spec/exchange/cache/memory_spec.rb +4 -2
  44. data/spec/exchange/cache/no_cache_spec.rb +2 -1
  45. data/spec/exchange/cache/rails_spec.rb +2 -1
  46. data/spec/exchange/cache/redis_spec.rb +2 -1
  47. data/spec/exchange/configuration_spec.rb +6 -2
  48. data/spec/exchange/core_extensions/array/cachify_spec.rb +2 -1
  49. data/spec/exchange/core_extensions/float/error_safe_spec.rb +2 -1
  50. data/spec/exchange/core_extensions/hash/cachify_spec.rb +2 -1
  51. data/spec/exchange/core_extensions/numeric/cachify_spec.rb +2 -1
  52. data/spec/exchange/core_extensions/numeric/conversability_spec.rb +2 -1
  53. data/spec/exchange/core_extensions/string/cachify_spec.rb +2 -1
  54. data/spec/exchange/core_extensions/symbol/cachify_spec.rb +2 -1
  55. data/spec/exchange/external_api/base_spec.rb +2 -1
  56. data/spec/exchange/external_api/call_spec.rb +2 -1
  57. data/spec/exchange/external_api/configuration_spec.rb +18 -2
  58. data/spec/exchange/external_api/ecb_spec.rb +2 -1
  59. data/spec/exchange/external_api/open_exchange_rates_spec.rb +2 -1
  60. data/spec/exchange/external_api/random_spec.rb +40 -0
  61. data/spec/exchange/external_api/xavier_media_spec.rb +2 -1
  62. data/spec/exchange/gem_loader_spec.rb +2 -1
  63. data/spec/exchange/helper_spec.rb +2 -1
  64. data/spec/exchange/iso_spec.rb +214 -189
  65. data/spec/exchange/money_spec.rb +65 -2
  66. data/spec/exchange/typecasting_spec.rb +2 -1
  67. data/spec/spec_helper.rb +3 -1
  68. metadata +10 -10
  69. data/benchmark/benchmark.rb +0 -50
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'rubygems'
2
3
  require 'bigdecimal'
3
4
  require 'open-uri'
@@ -11,4 +12,4 @@ require 'exchange/external_api'
11
12
  require 'exchange/cache'
12
13
  require 'exchange/configuration'
13
14
  require 'exchange/core_extensions'
14
- require 'exchange/typecasting'
15
+ require 'exchange/typecasting'
@@ -1,8 +1,9 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # The current version of the exchange gem
4
5
  #
5
- VERSION = '0.12.0'
6
+ VERSION = '1.0.0'
6
7
 
7
8
  # The root installation path of the gem
8
9
  # @version 0.5
@@ -22,4 +23,4 @@ module Exchange
22
23
  #
23
24
  NoCurrencyError = Class.new(ArgumentError)
24
25
 
25
- end
26
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'exchange/cache/configuration'
2
3
  require 'exchange/cache/base'
3
4
  require 'exchange/cache/memcached'
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'singleton'
2
3
  require 'forwardable'
3
4
 
@@ -99,4 +100,4 @@ module Exchange
99
100
  #
100
101
  CachingWithoutBlockError = Class.new(ArgumentError)
101
102
  end
102
- end
103
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
  # @author Beat Richartz
@@ -52,4 +53,4 @@ module Exchange
52
53
 
53
54
  end
54
55
  end
55
- end
56
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
 
@@ -73,4 +74,4 @@ module Exchange
73
74
 
74
75
  end
75
76
  end
76
- end
77
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
  # @author Beat Richartz
@@ -57,4 +58,4 @@ module Exchange
57
58
 
58
59
  end
59
60
  end
60
- end
61
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
  # @author Beat Richartz
@@ -86,4 +87,4 @@ module Exchange
86
87
 
87
88
  end
88
89
  end
89
- end
90
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
 
@@ -10,4 +11,4 @@ module Exchange
10
11
  NoCache = Class.new Base
11
12
 
12
13
  end
13
- end
14
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
  # @author Beat Richartz
@@ -42,4 +43,4 @@ module Exchange
42
43
 
43
44
  end
44
45
  end
45
- end
46
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cache
3
4
  # @author Beat Richartz
@@ -56,4 +57,4 @@ module Exchange
56
57
 
57
58
  end
58
59
  end
59
- end
60
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'singleton'
2
3
  require 'forwardable'
3
4
 
@@ -10,7 +11,7 @@ module Exchange
10
11
  attr_accessor :subclass
11
12
 
12
13
  def_delegators :instance, :subclass, :subclass=, :set
13
-
14
+
14
15
  def subclass_with_constantize
15
16
  self.subclass = parent_module.const_get camelize(self.subclass_without_constantize) unless !self.subclass_without_constantize || self.subclass_without_constantize.is_a?(Class)
16
17
  subclass_without_constantize
@@ -55,4 +56,4 @@ module Exchange
55
56
 
56
57
  end
57
58
 
58
- end
59
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  class << self
@@ -67,7 +68,7 @@ module Exchange
67
68
  end
68
69
 
69
70
  # The configuration defaults
70
- # @version 0.6
71
+ # @version 1.0
71
72
  # @since 0.6
72
73
  #
73
74
  DEFAULTS = {
@@ -75,7 +76,8 @@ module Exchange
75
76
  :subclass => ExternalAPI::XavierMedia,
76
77
  :retries => 5,
77
78
  :protocol => :http,
78
- :app_id => nil
79
+ :app_id => nil,
80
+ :fallback => ExternalAPI::Ecb
79
81
  },
80
82
  :cache => {
81
83
  :subclass => Cache::Memory,
@@ -143,8 +145,9 @@ module Exchange
143
145
  # @since 0.6
144
146
  # @version 0.6
145
147
  # @param [Hash] The hash to set the configuration to
146
- # @option [Symbol] :subclass The API subclass to use as a underscored symbol (will be camelized and constantized)
148
+ # @option [Symbol] :subclass The API subclass to use as a underscored symbol (will be camelized and constantized). Options available are :open_exchange_rates, :xavier_media, :ecb and :random (Random Rates for development use). You can build your own api subclass by following instructions under external_api/base.rb
147
149
  # @option [Integer] :retries The amount of retries on connection failure
150
+ # @option [Array,Symbol,Class] :fallback An array of fallbacks to be tried in the event when the standard API lookup fails or the standard API does not support the given currency
148
151
  # @example set the api to be ecb with a maximum of 8 retries on connection failure
149
152
  # configuration.api = { :subclass => :ecb, :retries => 8 }
150
153
  #
@@ -154,7 +157,7 @@ module Exchange
154
157
  # @since 0.6
155
158
  # @version 0.6
156
159
  # @param [Hash] The hash to set the configuration to
157
- # @option [Symbol] :subclass The Cache subclass to use as a underscored symbol (will be camelized and constantized)
160
+ # @option [Symbol] :subclass The Cache subclass to use as a underscored symbol (will be camelized and constantized). Options available are :memcached (via Dalli), :redis, :rails and :memory (default). You can build your own cache subclass by following instructions under cache/base.rb
158
161
  # @option [String] :host The cache connection host
159
162
  # @option [Integer] :port The cache connection port
160
163
  # @option [Symbol] :expire The expiration period for the cache, can be :daily or :hourly, defaults to daily
@@ -176,4 +179,4 @@ module Exchange
176
179
  install_getter :cache
177
180
 
178
181
  end
179
- end
182
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'exchange/core_extensions/numeric/conversability'
2
3
  require 'exchange/core_extensions/float/error_safe'
3
- require 'exchange/core_extensions/cachify'
4
+ require 'exchange/core_extensions/cachify'
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module Cachify
3
4
 
@@ -22,4 +23,4 @@ Symbol.send :include, Exchange::Cachify
22
23
  String.send :include, Exchange::Decachify
23
24
  Hash.send :include, Exchange::Cachify
24
25
  Array.send :include, Exchange::Cachify
25
- NilClass.send :include, Exchange::Cachify
26
+ NilClass.send :include, Exchange::Cachify
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # Make Floating Points forget about their incapabilities when dealing with money
@@ -41,4 +42,4 @@ module Exchange
41
42
 
42
43
  end
43
44
 
44
- Float.send(:include, Exchange::ErrorSafe)
45
+ Float.send(:include, Exchange::ErrorSafe)
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # The conversability module which will get included in Fixnum and Float, giving them the in currency instantiate methods
@@ -35,4 +36,4 @@ module Exchange
35
36
  end
36
37
 
37
38
  # include the Conversability methods in all number operations. Stack traces will indicate the module if something goes wrong.
38
- Numeric.send :include, Exchange::Conversability
39
+ Numeric.send :include, Exchange::Conversability
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'exchange/external_api/configuration'
2
3
  require 'exchange/external_api/base'
3
4
  require 'exchange/external_api/xml'
@@ -5,4 +6,5 @@ require 'exchange/external_api/json'
5
6
  require 'exchange/external_api/call'
6
7
  require 'exchange/external_api/open_exchange_rates'
7
8
  require 'exchange/external_api/xavier_media'
8
- require 'exchange/external_api/ecb'
9
+ require 'exchange/external_api/ecb'
10
+ require 'exchange/external_api/random'
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # The external API module. Every class Handling an API has to be placed here and inherit from base. It has to call an api and define
@@ -194,4 +195,4 @@ module Exchange
194
195
 
195
196
  end
196
197
  end
197
- end
198
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
 
@@ -75,4 +76,4 @@ module Exchange
75
76
  APIError = Class.new(StandardError)
76
77
 
77
78
  end
78
- end
79
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
  # @author Beat Richartz
@@ -8,9 +9,23 @@ module Exchange
8
9
  #
9
10
  class Configuration < Exchange::Configurable
10
11
 
11
- attr_accessor :retries, :app_id, :protocol
12
+ attr_accessor :retries, :app_id, :protocol, :fallback
12
13
 
13
- def_delegators :instance, :retries, :retries=, :app_id, :app_id=, :protocol, :protocol=
14
+ def_delegators :instance, :retries, :retries=, :app_id, :app_id=, :protocol, :protocol=, :fallback, :fallback=
15
+
16
+ def fallback_with_constantize
17
+ self.fallback = Array(fallback_without_constantize).map do |fb|
18
+ unless !fb || fb.is_a?(Class)
19
+ parent_module.const_get camelize(fb)
20
+ else
21
+ fb
22
+ end
23
+ end
24
+
25
+ fallback_without_constantize
26
+ end
27
+ alias_method :fallback_without_constantize, :fallback
28
+ alias_method :fallback, :fallback_with_constantize
14
29
 
15
30
  def parent_module
16
31
  ExternalAPI
@@ -22,4 +37,4 @@ module Exchange
22
37
 
23
38
  end
24
39
  end
25
- end
40
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
 
@@ -127,4 +128,4 @@ module Exchange
127
128
 
128
129
  end
129
130
  end
130
- end
131
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
 
@@ -19,4 +20,4 @@ module Exchange
19
20
 
20
21
  end
21
22
  end
22
- end
23
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
  # The Open Exchange Rates API class, handling communication with the Open Source Currency bot API
@@ -62,4 +63,4 @@ module Exchange
62
63
 
63
64
  end
64
65
  end
65
- end
66
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Exchange
3
+ module ExternalAPI
4
+
5
+ # The Random API class, which is only intended for use in development mode. Returns random exchange rates.
6
+ # @author Beat Richartz
7
+ # @version 1.0
8
+ # @since 1.0
9
+ #
10
+ class Random < Base
11
+
12
+ CURRENCIES = Exchange::ISO.currencies
13
+ RANDOM_RATES = lambda { Hash[*CURRENCIES.zip(CURRENCIES.size.times.map{|i| rand}).flatten] }
14
+
15
+ # Updates the rates with new random ones
16
+ # The call gets cached for a maximum of 24 hours.
17
+ # @version 0.7
18
+ # @param [Hash] opts Options to define for the API Call
19
+ # @option opts [Time, String] :at a historical date to get the exchange rates for
20
+ # @example Update the currency bot API to use the file of March 2, 2010
21
+ # Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
22
+ #
23
+ def update(opts={})
24
+ @base = :usd
25
+ @rates = RANDOM_RATES.call
26
+ @timestamp = Time.now.to_i
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
 
@@ -89,4 +90,4 @@ module Exchange
89
90
 
90
91
  end
91
92
  end
92
- end
93
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
  module ExternalAPI
3
4
 
@@ -19,4 +20,4 @@ module Exchange
19
20
 
20
21
  end
21
22
  end
22
- end
23
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # The gem loader takes care of loading gems without adding too many unnecessary dependencies to the gem
@@ -32,4 +33,4 @@ module Exchange
32
33
  raise GemNotFoundError.new("You specified #{@gem} to be used with Exchange, yet it is not loadable. Please install #{@gem} to be able to use it with Exchange")
33
34
  end
34
35
  end
35
- end
36
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'singleton'
2
3
  require 'forwardable'
3
4
 
@@ -31,4 +32,4 @@ module Exchange
31
32
 
32
33
  end
33
34
 
34
- end
35
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'singleton'
2
3
  require 'forwardable'
3
4
  require 'yaml'
@@ -22,9 +23,16 @@ module Exchange
22
23
 
23
24
  def install_operation op
24
25
  self.class_eval <<-EOV
25
- def #{op}(amount, currency, precision=nil)
26
+ def #{op}(amount, currency, precision=nil, opts={})
26
27
  minor = definitions[currency][:minor_unit]
27
- (amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, precision_for(amount, currency))).#{op}(precision || minor)
28
+ money = amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, precision_for(amount, currency))
29
+ if opts[:psych] && minor > 0
30
+ money.#{op}(0) - BigDecimal.new((1.0/(10**minor)).to_s)
31
+ elsif opts[:psych]
32
+ (((money.#{op}(0) / BigDecimal.new("10.0")).#{op}(0)) - BigDecimal.new("0.1")) * BigDecimal.new("10")
33
+ else
34
+ money.#{op}(precision || minor)
35
+ end
28
36
  end
29
37
  EOV
30
38
  end
@@ -92,7 +100,7 @@ module Exchange
92
100
  # @param [BigDecimal, Fixed, Float] amount The amount of currency you want to stringify
93
101
  # @param [String, Symbol] currency The currency you want to stringify
94
102
  # @param [Hash] opts The options for formatting
95
- # @option opts [Boolean] :amount_only Whether you want to have the currency in the string or not
103
+ # @option opts [Boolean] :format The format to put the string out in: :amount for only the amount, :symbol for a string with a currency symbol
96
104
  # @return [String] The formatted string
97
105
  # @example Convert a currency to a string
98
106
  # Exchange::ISO.stringify(49.567, :usd) #=> "USD 49.57"
@@ -104,9 +112,22 @@ module Exchange
104
112
  # Exchange::ISO.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
105
113
  #
106
114
  def stringify(amount, currency, opts={})
107
- format = "%.#{definitions[currency][:minor_unit]}f"
108
- pre = [opts[:amount_only] && '', opts[:symbol] && (definitions[currency][:symbol] || currency.to_s.upcase), currency.to_s.upcase + ' '].detect{|a| a.is_a?(String)}
109
- "#{pre}#{format % amount}"
115
+ definition = definitions[currency]
116
+ separators = definition[:separators] || {}
117
+ format = "%.#{definition[:minor_unit]}f"
118
+ string = format % amount
119
+ major, minor = string.split('.')
120
+
121
+ if separators[:major]
122
+ major.reverse!
123
+ major.gsub!(/(\d{3})(?=.)/) { $1 + separators[:major] }
124
+ major.reverse!
125
+ end
126
+
127
+ string = minor ? major + (separators[:minor] || '.') + minor : major
128
+ pre = [opts[:format] == :amount && '', opts[:format] == :symbol && definition[:symbol], currency.to_s.upcase + ' '].detect{|a| a.is_a?(String)}
129
+
130
+ "#{pre}#{string}"
110
131
  end
111
132
 
112
133
  # Use this to round a currency amount. This allows us to round exactly to the number of minors the currency has in the
@@ -148,12 +169,7 @@ module Exchange
148
169
  new_hsh = Hash.new
149
170
 
150
171
  hsh.each_pair do |k,v|
151
- if v.is_a?(Hash)
152
- v.keys.each do |key|
153
- v[key.to_sym] = v.delete(key)
154
- end
155
- end
156
-
172
+ v = symbolize_keys v if v.is_a?(Hash)
157
173
  new_hsh[k.downcase.to_sym] = v
158
174
  end
159
175
 
@@ -172,4 +188,4 @@ module Exchange
172
188
  end
173
189
 
174
190
  end
175
- end
191
+ end