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
  # Top Level Module of the the gem.
2
3
  # @author Beat Richartz
3
4
  # @version 0.9
@@ -88,9 +89,17 @@ module Exchange
88
89
  if api_supports_currency?(other)
89
90
  opts = { :at => time, :from => self }.merge(options)
90
91
  Money.new(api.new.convert(value, currency, other, opts), other, opts)
92
+ elsif fallback!
93
+ to other, options
91
94
  else
92
95
  raise_no_rate_error(other)
93
96
  end
97
+ rescue ExternalAPI::APIError
98
+ if fallback!
99
+ to other, options
100
+ else
101
+ raise
102
+ end
94
103
  end
95
104
  alias :in :to
96
105
 
@@ -103,7 +112,8 @@ module Exchange
103
112
  #
104
113
  def install_operation op
105
114
  define_method op do |*precision|
106
- Exchange::Money.new(ISO.send(op, self.value, self.currency, precision.first), currency, :at => time, :from => self)
115
+ psych = precision.first == :psych
116
+ Exchange::Money.new(ISO.send(op, self.value, self.currency, psych ? nil : precision.first, {:psych => psych}), currency, :at => time, :from => self)
107
117
  end
108
118
  end
109
119
 
@@ -296,18 +306,34 @@ module Exchange
296
306
  # Exchange::Money.new(45, :jpy).to_s #=> "JPY 45"
297
307
  # @example Convert a currency with a three decimal minor to a string
298
308
  # Exchange::Money.new(34.34, :omr).to_s #=> "OMR 34.340"
299
- # @example Convert a currency to a string without the currency
300
- # Exchange::ISO.stringif(34.34, :omr).to_s(:iso) #=> "34.340"
309
+ # @example Convert a currency with a three decimal minor to a string with a currency symbol
310
+ # Exchange::Money.new(34.34, :usd).to_s(:symbol) #=> "$34.34"
311
+ # @example Convert a currency with a three decimal minor to a string with just the amount
312
+ # Exchange::Money.new(34.34, :omr).to_s(:amount) #=> "34.340"
301
313
  #
302
314
  def to_s format=:currency
303
- [
304
- format == :currency && ISO.stringify(value, currency),
305
- format == :amount && ISO.stringify(value, currency, :amount_only => true)
306
- ].detect{|l| l.is_a?(String) }
315
+ ISO.stringify(value, currency, :format => format)
307
316
  end
308
317
 
309
318
  private
310
319
 
320
+ # Fallback to the next api defined in the api fallbacks. Changes the api for the given instance
321
+ # @return [Boolean] true if the fallback was successful, false if not
322
+ # @since 1.0
323
+ # @version 1.0
324
+ #
325
+ def fallback!
326
+ fallback = Exchange.configuration.api.fallback
327
+ new_api = fallback.index(api) ? fallback[fallback.index(api) + 1] : fallback.first
328
+
329
+ if new_api
330
+ @api = new_api
331
+ return true
332
+ end
333
+
334
+ return false
335
+ end
336
+
311
337
  # determine if another given object is an instance of Exchange::Money
312
338
  # @param [Object] other The object to be tested against
313
339
  # @return [Boolean] true if the other is an instance of Exchange::Money, false if not
@@ -363,7 +389,7 @@ module Exchange
363
389
  # @version 0.7.2
364
390
  #
365
391
  def raise_no_rate_error other
366
- raise NoRateError.new("Cannot convert to #{other} because the defined api does not provide a rate")
392
+ raise NoRateError.new("Cannot convert to #{other} because the defined api nor the fallbacks provide a rate")
367
393
  end
368
394
 
369
395
  end
@@ -372,4 +398,4 @@ module Exchange
372
398
  #
373
399
  ImplicitConversionError = Class.new(StandardError)
374
400
 
375
- end
401
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Exchange
2
3
 
3
4
  # Allows to access properties of an object as currencies
@@ -159,4 +160,4 @@ module Exchange
159
160
 
160
161
  end
161
162
 
162
- end
163
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::Base" do
@@ -26,4 +27,4 @@ describe "Exchange::Cache::Base" do
26
27
  end
27
28
  end
28
29
  end
29
- end
30
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::Configuration" do
@@ -51,12 +52,14 @@ describe "Exchange::Cache::Configuration" do
51
52
  end
52
53
  it "should do so for the host" do
53
54
  subject.subclass.client.should_not be_nil
55
+ subject.subclass.client
54
56
  subject.subclass.instance.instance_variable_get("@client").should_not be_nil
55
57
  subject.host = 'new'
56
58
  subject.subclass.instance.instance_variable_get("@client").should be_nil
57
59
  end
58
60
  it "should do so for the port" do
59
61
  subject.subclass.client.should_not be_nil
62
+ subject.subclass.client
60
63
  subject.subclass.instance.instance_variable_get("@client").should_not be_nil
61
64
  subject.port = 112
62
65
  subject.subclass.instance.instance_variable_get("@client").should be_nil
@@ -91,4 +94,4 @@ describe "Exchange::Cache::Configuration" do
91
94
  end
92
95
  end
93
96
 
94
- end
97
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::File" do
@@ -70,4 +71,4 @@ describe "Exchange::Cache::File" do
70
71
  end
71
72
  end
72
73
  end
73
- end
74
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::CacheDalli::Client" do
@@ -119,4 +120,4 @@ describe "Exchange::CacheDalli::Client" do
119
120
  end
120
121
  end
121
122
  end
122
- end
123
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::Memory" do
@@ -37,7 +38,8 @@ describe "Exchange::Cache::Memory" do
37
38
  end
38
39
  it "should unset all expired instance variables" do
39
40
  subject.send(:clean!)
40
- subject.instance_variables.reject{|i| i.to_s == "@helper" }.map{|i| subject.instance_variable_get(i) }.compact.sort_by(&:to_s).should == %W(Valid1 Valid2 Valid3 Valid4).sort
41
+ ivars = subject.instance_variables.reject{|i| i.to_s == "@helper" }.map{|i| subject.instance_variable_get(i) }.compact.sort_by(&:to_s)
42
+ ivars.select{|i| i.is_a?(String) }.should be_eql(%W(Valid1 Valid2 Valid3 Valid4))
41
43
  end
42
44
  end
43
45
  describe "cached" do
@@ -119,4 +121,4 @@ describe "Exchange::Cache::Memory" do
119
121
  end
120
122
  end
121
123
  end
122
- end
124
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::NoCache" do
@@ -20,4 +21,4 @@ describe "Exchange::Cache::NoCache" do
20
21
  lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
21
22
  end
22
23
  end
23
- end
24
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cache::Rails" do
@@ -67,4 +68,4 @@ describe "Exchange::Cache::Rails" do
67
68
  end
68
69
  end
69
70
  end
70
- end
71
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
  require 'redis'
3
4
 
@@ -83,4 +84,4 @@ describe "Exchange::Cache::Redis" do
83
84
  end
84
85
  end
85
86
  end
86
- end
87
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Configuration" do
@@ -28,7 +29,8 @@ describe "Exchange::Configuration" do
28
29
  Exchange.configuration = Exchange::Configuration.new {|c|
29
30
  c.api = {
30
31
  :subclass => :xavier_media,
31
- :retries => 60
32
+ :retries => 60,
33
+ :fallback => :open_exchange_rates
32
34
  }
33
35
  c.cache = {
34
36
  :subclass => :redis
@@ -36,6 +38,7 @@ describe "Exchange::Configuration" do
36
38
  }
37
39
  Exchange.configuration.api.subclass.should == Exchange::ExternalAPI::XavierMedia
38
40
  Exchange.configuration.api.retries.should == 60
41
+ Exchange.configuration.api.fallback.should == [Exchange::ExternalAPI::OpenExchangeRates]
39
42
  Exchange.configuration.cache.subclass.should == Exchange::Cache::Redis
40
43
  end
41
44
  it "should allow to be set directly" do
@@ -66,6 +69,7 @@ describe "Exchange::Configuration" do
66
69
  subject.api.subclass.should == Exchange::ExternalAPI::XavierMedia
67
70
  subject.api.retries.should == 5
68
71
  subject.api.app_id.should be_nil
72
+ subject.api.fallback.should == [Exchange::ExternalAPI::Ecb]
69
73
  subject.cache.subclass.should == Exchange::Cache::Memory
70
74
  subject.cache.host.should be_nil
71
75
  subject.cache.port.should be_nil
@@ -76,4 +80,4 @@ describe "Exchange::Configuration" do
76
80
  after(:all) do
77
81
  Exchange.configuration.reset
78
82
  end
79
- end
83
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cachify" do
@@ -9,4 +10,4 @@ describe "Exchange::Cachify" do
9
10
  end
10
11
  end
11
12
 
12
- end
13
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ErrorSafe" do
@@ -46,4 +47,4 @@ describe "Exchange::ErrorSafe" do
46
47
  end
47
48
  end
48
49
  end
49
- end
50
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cachify" do
@@ -9,4 +10,4 @@ describe "Exchange::Cachify" do
9
10
  end
10
11
  end
11
12
 
12
- end
13
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cachify" do
@@ -23,4 +24,4 @@ describe "Exchange::Cachify" do
23
24
  end
24
25
  end
25
26
 
26
- end
27
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Conversability" do
@@ -101,4 +102,4 @@ describe "Exchange::Conversability" do
101
102
  lambda { BigDecimal.new("3.25").in(:zzz) }.should raise_error(Exchange::NoCurrencyError, "zzz is not a currency nor a country code matchable to a currency")
102
103
  end
103
104
  end
104
- end
105
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cachify" do
@@ -56,4 +57,4 @@ describe "Exchange::Cachify" do
56
57
  end
57
58
  end
58
59
 
59
- end
60
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::Cachify" do
@@ -9,4 +10,4 @@ describe "Exchange::Cachify" do
9
10
  end
10
11
  end
11
12
 
12
- end
13
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ExternalAPI::Base" do
@@ -36,4 +37,4 @@ describe "Exchange::ExternalAPI::Base" do
36
37
  subject.convert(80,:chf, :eur, :at => time).round(2).should == 49.6
37
38
  end
38
39
  end
39
- end
40
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ExternalAPI::Call" do
@@ -81,4 +82,4 @@ describe "Exchange::ExternalAPI::Call" do
81
82
  end
82
83
  end
83
84
  end
84
- end
85
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ExternalAPI::Configuration" do
@@ -23,14 +24,28 @@ describe "Exchange::ExternalAPI::Configuration" do
23
24
  end
24
25
  end
25
26
 
27
+ describe "fallback constantize" do
28
+ it "should automatically constantize a single fallback and wrap it in an array" do
29
+ subject.fallback = :xavier_media
30
+
31
+ subject.fallback.should == [Exchange::ExternalAPI::XavierMedia]
32
+ end
33
+ it "should automatically constantize the fallbacks" do
34
+ subject.fallback = [:xavier_media, :random]
35
+
36
+ subject.fallback.should == [Exchange::ExternalAPI::XavierMedia, Exchange::ExternalAPI::Random]
37
+ end
38
+ end
39
+
26
40
  describe "set" do
27
41
  before(:each) do
28
- @return = subject.set :subclass => :xavier_media, :retries => 55, :app_id => "KEY"
42
+ @return = subject.set :subclass => :xavier_media, :retries => 55, :app_id => "KEY", :fallback => [:random, :ecb]
29
43
  end
30
44
  it "should set the options given" do
31
45
  subject.subclass.should == Exchange::ExternalAPI::XavierMedia
32
46
  subject.retries.should == 55
33
47
  subject.app_id.should == 'KEY'
48
+ subject.fallback.should == [Exchange::ExternalAPI::Random, Exchange::ExternalAPI::Ecb]
34
49
  end
35
50
  it "should return self" do
36
51
  @return.should == subject
@@ -46,7 +61,8 @@ describe "Exchange::ExternalAPI::Configuration" do
46
61
  subject.subclass.should == Exchange::ExternalAPI::XavierMedia
47
62
  subject.retries.should == 5
48
63
  subject.app_id.should be_nil
64
+ subject.fallback.should == [Exchange::ExternalAPI::Ecb]
49
65
  end
50
66
  end
51
67
 
52
- end
68
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ExternalAPI::Ecb" do
@@ -59,4 +60,4 @@ describe "Exchange::ExternalAPI::Ecb" do
59
60
  subject.convert(70, :sek, :usd, :at => Time.gm(2011,9,9)).round(2).should == 10.35
60
61
  end
61
62
  end
62
- end
63
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'spec_helper'
2
3
 
3
4
  describe "Exchange::ExternalAPI::OpenExchangeRates" do
@@ -67,4 +68,4 @@ describe "Exchange::ExternalAPI::OpenExchangeRates" do
67
68
  subject.convert(70, :sek, :usd, :at => Time.gm(Time.now.year - 1,3,1)).round(2).should == 10.38
68
69
  end
69
70
  end
70
- end
71
+ end
@@ -0,0 +1,40 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe "Exchange::ExternalAPI::OpenExchangeRates" do
5
+ before(:all) do
6
+ Exchange.configuration = Exchange::Configuration.new{|c|
7
+ c.cache = {
8
+ :subclass => :no_cache
9
+ }
10
+ c.api = {
11
+ :subclass => :random
12
+ }
13
+ }
14
+ end
15
+ after(:all) do
16
+ Exchange.configuration.reset
17
+ end
18
+ describe "updating rates" do
19
+ subject { Exchange::ExternalAPI::Random.new }
20
+ it "should have usd as base currency" do
21
+ subject.update
22
+ subject.base.should == :usd
23
+ end
24
+ it "should set the timestamp from time.now" do
25
+ time = Time.now
26
+ time.stub! :now => time
27
+ subject.update
28
+ subject.timestamp.should == time.to_i
29
+ end
30
+ end
31
+ describe "rates" do
32
+ subject { Exchange::ExternalAPI::Random.new }
33
+ it "should provide a rate for every ISO currency" do
34
+ subject.update
35
+ Exchange::ISO.currencies.each do |c|
36
+ subject.rates[c].should_not be_nil
37
+ end
38
+ end
39
+ end
40
+ end