exchange 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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