exchange 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +17 -8
  2. data/Gemfile.lock +12 -6
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +15 -12
  5. data/VERSION +1 -1
  6. data/exchange.gemspec +13 -23
  7. data/lib/core_extensions/conversability.rb +5 -4
  8. data/lib/exchange.rb +6 -11
  9. data/lib/exchange/base.rb +19 -0
  10. data/lib/exchange/cache/base.rb +57 -32
  11. data/lib/exchange/cache/file.rb +41 -45
  12. data/lib/exchange/cache/memcached.rb +28 -29
  13. data/lib/exchange/cache/no_cache.rb +4 -24
  14. data/lib/exchange/cache/rails.rb +26 -26
  15. data/lib/exchange/cache/redis.rb +30 -33
  16. data/lib/exchange/configuration.rb +167 -73
  17. data/lib/exchange/currency.rb +61 -30
  18. data/lib/exchange/external_api.rb +2 -0
  19. data/lib/exchange/external_api/base.rb +11 -5
  20. data/lib/exchange/external_api/call.rb +10 -7
  21. data/lib/exchange/external_api/currency_bot.rb +9 -5
  22. data/lib/exchange/external_api/ecb.rb +29 -13
  23. data/lib/exchange/external_api/json.rb +22 -0
  24. data/lib/exchange/external_api/xavier_media.rb +6 -5
  25. data/lib/exchange/external_api/xml.rb +22 -0
  26. data/lib/exchange/gem_loader.rb +35 -0
  27. data/lib/exchange/helper.rb +22 -15
  28. data/lib/exchange/iso_4217.rb +55 -44
  29. data/spec/core_extensions/conversability_spec.rb +2 -2
  30. data/spec/exchange/cache/base_spec.rb +7 -7
  31. data/spec/exchange/cache/file_spec.rb +19 -18
  32. data/spec/exchange/cache/memcached_spec.rb +29 -26
  33. data/spec/exchange/cache/no_cache_spec.rb +12 -6
  34. data/spec/exchange/cache/rails_spec.rb +13 -9
  35. data/spec/exchange/cache/redis_spec.rb +24 -24
  36. data/spec/exchange/configuration_spec.rb +38 -30
  37. data/spec/exchange/currency_spec.rb +33 -21
  38. data/spec/exchange/external_api/base_spec.rb +3 -1
  39. data/spec/exchange/external_api/call_spec.rb +5 -1
  40. data/spec/exchange/external_api/currency_bot_spec.rb +5 -1
  41. data/spec/exchange/external_api/ecb_spec.rb +9 -5
  42. data/spec/exchange/external_api/xavier_media_spec.rb +5 -1
  43. data/spec/exchange/gem_loader_spec.rb +29 -0
  44. data/spec/spec_helper.rb +1 -1
  45. metadata +12 -87
@@ -7,14 +7,20 @@ describe "Exchange::Cache::Rails" do
7
7
  end
8
8
  subject { Exchange::Cache::NoCache }
9
9
  before(:each) do
10
- Exchange::Configuration.define do |c|
11
- c.cache = false
12
- end
10
+ Exchange.configuration = Exchange::Configuration.new { |c|
11
+ c.cache = {
12
+ :class => :no_cache
13
+ }
14
+ }
13
15
  end
14
16
  after(:each) do
15
- Exchange::Configuration.define do |c|
16
- c.cache = :memcached
17
- end
17
+ Exchange.configuration = Exchange::Configuration.new { |c|
18
+ c.cache = {
19
+ :class => :memcached,
20
+ :host => 'localhost',
21
+ :port => 11211
22
+ }
23
+ }
18
24
  end
19
25
  describe "cached" do
20
26
  it "should directly call the block" do
@@ -5,16 +5,20 @@ describe "Exchange::Cache::Rails" do
5
5
  class ::Rails
6
6
  end
7
7
  end
8
- subject { Exchange::Cache::Rails }
8
+ subject { Exchange::Cache::Rails.instance }
9
9
  before(:each) do
10
- Exchange::Configuration.define do |c|
11
- c.cache = :rails
12
- end
10
+ Exchange.configuration = Exchange::Configuration.new { |c|
11
+ c.cache = {
12
+ :class => :rails
13
+ }
14
+ }
13
15
  end
14
16
  after(:each) do
15
- Exchange::Configuration.define do |c|
16
- c.cache = :memcached
17
- end
17
+ Exchange.configuration = Exchange::Configuration.new { |c|
18
+ c.cache = {
19
+ :class => :memcached
20
+ }
21
+ }
18
22
  end
19
23
  describe "client" do
20
24
  let(:client) { mock('rails_cache') }
@@ -41,13 +45,13 @@ describe "Exchange::Cache::Rails" do
41
45
  end
42
46
  context "with an hourly cache" do
43
47
  before(:each) do
44
- Exchange::Configuration.update = :hourly
48
+ Exchange.configuration.cache.expire = :hourly
45
49
  subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
46
50
  ::Rails.should_receive(:cache).and_return(client)
47
51
  client.should_receive(:fetch).with('KEY', :expires_in => 3600).and_return "{\"RESULT\":\"YAY\"}"
48
52
  end
49
53
  after(:each) do
50
- Exchange::Configuration.update = :daily
54
+ Exchange.configuration.cache.expire = :daily
51
55
  end
52
56
  it "should return the JSON loaded result" do
53
57
  subject.cached('API_CLASS') { 'something' }.should == "{\"RESULT\":\"YAY\"}"
@@ -1,25 +1,30 @@
1
1
  require 'spec_helper'
2
+ require 'redis'
2
3
 
3
4
  describe "Exchange::Cache::Redis" do
4
- subject { Exchange::Cache::Redis }
5
+ subject { Exchange::Cache::Redis.instance }
5
6
  before(:each) do
6
- Exchange::Configuration.define do |c|
7
- c.cache = :redis
8
- c.cache_host = 'HOST'
9
- c.cache_port = 'PORT'
7
+ Exchange.configuration = Exchange::Configuration.new do |c|
8
+ c.cache = {
9
+ :class => :redis,
10
+ :host => 'HOST',
11
+ :port => 'PORT'
12
+ }
10
13
  end
11
14
  end
12
15
  after(:each) do
13
- Exchange::Configuration.define do |c|
14
- c.cache = :memcached
15
- c.cache_host = 'localhost'
16
- c.cache_port = 11211
16
+ Exchange.configuration = Exchange::Configuration.new do |c|
17
+ c.cache = {
18
+ :class => :memcached,
19
+ :host => 'localhost',
20
+ :port => 11211
21
+ }
17
22
  end
18
23
  end
19
24
  describe "client" do
20
25
  let(:client) { mock('redis') }
21
26
  after(:each) do
22
- subject.send(:remove_class_variable, "@@client")
27
+ subject.instance_variable_set "@client", nil
23
28
  end
24
29
  it "should set up a client on the specified host and port for the cache" do
25
30
  ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
@@ -27,17 +32,17 @@ describe "Exchange::Cache::Redis" do
27
32
  end
28
33
  end
29
34
  describe "cached" do
35
+ let(:client) { mock('redis', :get => nil) }
36
+ before(:each) do
37
+ ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
38
+ end
39
+ after(:each) do
40
+ subject.instance_variable_set "@client", nil
41
+ end
30
42
  it "should raise an error if no block was given" do
31
43
  lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
32
44
  end
33
45
  context "when a cached result exists" do
34
- let(:client) { mock('redis') }
35
- before(:each) do
36
- ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
37
- end
38
- after(:each) do
39
- subject.send(:remove_class_variable, "@@client")
40
- end
41
46
  context "when loading json" do
42
47
  before(:each) do
43
48
  subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
@@ -62,15 +67,10 @@ describe "Exchange::Cache::Redis" do
62
67
  end
63
68
  end
64
69
  context "when no cached result exists" do
65
- let(:client) { mock('redis') }
66
70
  before(:each) do
67
71
  subject.should_receive(:key).with('API_CLASS', {}).at_most(3).times.and_return('KEY')
68
- ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
69
72
  client.should_receive(:get).with('KEY').and_return nil
70
73
  end
71
- after(:each) do
72
- subject.send(:remove_class_variable, "@@client")
73
- end
74
74
  context "with daily cache" do
75
75
  it "should call the block and set and return the result" do
76
76
  client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}").once
@@ -80,10 +80,10 @@ describe "Exchange::Cache::Redis" do
80
80
  end
81
81
  context "with hourly cache" do
82
82
  before(:each) do
83
- Exchange::Configuration.update = :hourly
83
+ Exchange.configuration.cache.expire = :hourly
84
84
  end
85
85
  after(:each) do
86
- Exchange::Configuration.update = :daily
86
+ Exchange.configuration.cache.expire = :daily
87
87
  end
88
88
  it "should call the block and set and return the result" do
89
89
  client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}").once
@@ -1,47 +1,55 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Exchange::Configuration" do
4
- let(:subject) { Exchange::Configuration }
4
+ let(:subject) { Exchange::Configuration.new }
5
5
  it "should have a standard configuration" do
6
- subject.api.should == :xavier_media
7
- subject.api_class.should == Exchange::ExternalAPI::XavierMedia
8
- subject.cache.should == :memcached
9
- subject.cache_class.should == Exchange::Cache::Memcached
10
- subject.cache_host.should == 'localhost'
11
- subject.cache_port.should == 11211
12
- subject.retries.should == 5
6
+ subject.api.retries.should == 5
7
+ subject.api.subclass.should == Exchange::ExternalAPI::XavierMedia
8
+ subject.cache.subclass.should == Exchange::Cache::Memcached
9
+ subject.cache.host.should == 'localhost'
10
+ subject.cache.port.should == 11211
11
+ subject.cache.expire.should == :daily
13
12
  end
14
13
  it "should respond to all configuration getters and setters" do
15
- [:api, :retries, :allow_mixed_operations, :cache, :cache_host, :cache_port, :update].each do |k|
14
+ [:api, :allow_mixed_operations, :cache].each do |k|
16
15
  subject.should be_respond_to(k)
17
16
  subject.should be_respond_to(:"#{k}=")
18
17
  end
19
18
  end
20
- it "should allow to be defined with a block" do
21
- subject.define do |c|
22
- c.api = :xavier_media
23
- c.cache = :redis
24
- c.retries = 60
19
+ it 'should respond to nested getters and setters for the api and the cache' do
20
+ {:api => [:subclass, :retries], :cache => [:subclass, :host, :port, :expire]}.each do |k,m|
21
+ m.each do |meth|
22
+ subject.send(k).should be_respond_to(meth)
23
+ subject.send(k).should be_respond_to(:"#{meth}=")
24
+ end
25
25
  end
26
- subject.api.should == :xavier_media
27
- subject.api_class.should == Exchange::ExternalAPI::XavierMedia
28
- subject.cache.should == :redis
29
- subject.cache_class.should == Exchange::Cache::Redis
30
- subject.retries.should == 60
26
+ end
27
+ it "should allow to be defined with a block" do
28
+ Exchange.configuration = Exchange::Configuration.new {|c|
29
+ c.api = {
30
+ :subclass => :xavier_media,
31
+ :retries => 60
32
+ }
33
+ c.cache = {
34
+ :subclass => :redis
35
+ }
36
+ }
37
+ Exchange.configuration.api.subclass.should == Exchange::ExternalAPI::XavierMedia
38
+ Exchange.configuration.api.retries.should == 60
39
+ Exchange.configuration.cache.subclass.should == Exchange::Cache::Redis
31
40
  end
32
41
  it "should allow to be set directly" do
33
- subject.api = :paypal
34
- subject.cache = :yml
35
- subject.retries = 1
36
- subject.api.should == :paypal
37
- subject.cache.should == :yml
38
- subject.retries.should == 1
42
+ subject.api = {
43
+ :subclass => :ecb,
44
+ :retries => 1
45
+ }
46
+ subject.api.subclass.should == Exchange::ExternalAPI::Ecb
47
+ subject.api.retries.should == 1
39
48
  end
40
49
  after(:all) do
41
- subject.api = :currency_bot
42
- subject.cache = :memcached
43
- subject.cache_host = 'localhost'
44
- subject.cache_port = 11211
45
- subject.retries = 5
50
+ subject.api = {
51
+ :subclass => :currency_bot,
52
+ :retries => 5
53
+ }
46
54
  end
47
55
  end
@@ -3,13 +3,22 @@ require 'spec_helper'
3
3
  describe "Exchange::Currency" do
4
4
  subject { Exchange::Currency.new(40, :usd) }
5
5
  before(:all) do
6
- Exchange::Configuration.define do |c|
7
- c.api = :currency_bot
8
- c.cache = false
6
+ Exchange.configuration = Exchange::Configuration.new do |c|
7
+ c.api = {
8
+ :subclass => :currency_bot
9
+ }
10
+ c.cache = {
11
+ :subclass => :no_cache
12
+ }
13
+ c.allow_mixed_operations = true
9
14
  end
10
15
  end
11
16
  after(:all) do
12
- Exchange::Configuration.cache = :memcached
17
+ Exchange.configuration = Exchange::Configuration.new do |c|
18
+ c.api = {
19
+ :subclass => :memcached
20
+ }
21
+ end
13
22
  end
14
23
  it "should initialize with a number and a currency" do
15
24
  subject.value.should == 40
@@ -37,15 +46,15 @@ describe "Exchange::Currency" do
37
46
  (subject + Exchange::Currency.new(30, :sek)).currency.should == :usd
38
47
  end
39
48
  it "should raise when currencies get mixed and the configuration does not allow it" do
40
- Exchange::Configuration.allow_mixed_operations = false
49
+ Exchange.configuration.allow_mixed_operations = false
41
50
  lambda { subject + Exchange::Currency.new(30, :chf) }.should raise_error(Exchange::CurrencyMixError)
42
- Exchange::Configuration.allow_mixed_operations = true
51
+ Exchange.configuration.allow_mixed_operations = true
43
52
  end
44
53
  it "should not raise when currencies get mixed and the configuration does not allow if the other currency is the same" do
45
- Exchange::Configuration.allow_mixed_operations = false
54
+ Exchange.configuration.allow_mixed_operations = false
46
55
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
47
56
  lambda { subject + Exchange::Currency.new(30, :usd) }.should_not raise_error
48
- Exchange::Configuration.allow_mixed_operations = true
57
+ Exchange.configuration.allow_mixed_operations = true
49
58
  end
50
59
  end
51
60
  describe "- other" do
@@ -57,19 +66,20 @@ describe "Exchange::Currency" do
57
66
  end
58
67
  it "should be able to subtract another currency value" do
59
68
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
69
+ Exchange.configuration.allow_mixed_operations = true
60
70
  (subject + Exchange::Currency.new(10, :chf)).value.round(2).should == 50.96
61
71
  (subject + Exchange::Currency.new(23.3, :eur)).currency.should == :usd
62
72
  end
63
73
  it "should raise when currencies get mixed and the configuration does not allow it" do
64
- Exchange::Configuration.allow_mixed_operations = false
74
+ Exchange.configuration.allow_mixed_operations = false
65
75
  lambda { subject - Exchange::Currency.new(30, :chf) }.should raise_error(Exchange::CurrencyMixError)
66
- Exchange::Configuration.allow_mixed_operations = true
76
+ Exchange.configuration.allow_mixed_operations = true
67
77
  end
68
78
  it "should not raise when currencies get mixed and the configuration does not allow if the other currency is the same" do
69
- Exchange::Configuration.allow_mixed_operations = false
79
+ Exchange.configuration.allow_mixed_operations = false
70
80
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
71
81
  lambda { subject - Exchange::Currency.new(30, :usd) }.should_not raise_error
72
- Exchange::Configuration.allow_mixed_operations = true
82
+ Exchange.configuration.allow_mixed_operations = true
73
83
  end
74
84
  end
75
85
  describe "* other" do
@@ -81,19 +91,20 @@ describe "Exchange::Currency" do
81
91
  end
82
92
  it "should be able to multiply by another currency value" do
83
93
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
94
+ Exchange.configuration.allow_mixed_operations = true
84
95
  (subject * Exchange::Currency.new(10, :chf)).value.round(1).should == 438.3
85
96
  (subject * Exchange::Currency.new(23.3, :eur)).currency.should == :usd
86
97
  end
87
98
  it "should raise when currencies get mixed and the configuration does not allow it" do
88
- Exchange::Configuration.allow_mixed_operations = false
99
+ Exchange.configuration.allow_mixed_operations = false
89
100
  lambda { subject * Exchange::Currency.new(30, :chf) }.should raise_error(Exchange::CurrencyMixError)
90
- Exchange::Configuration.allow_mixed_operations = true
101
+ Exchange.configuration.allow_mixed_operations = true
91
102
  end
92
103
  it "should not raise when currencies get mixed and the configuration does not allow if the other currency is the same" do
93
- Exchange::Configuration.allow_mixed_operations = false
104
+ Exchange.configuration.allow_mixed_operations = false
94
105
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
95
106
  lambda { subject * Exchange::Currency.new(30, :usd) }.should_not raise_error
96
- Exchange::Configuration.allow_mixed_operations = true
107
+ Exchange.configuration.allow_mixed_operations = true
97
108
  end
98
109
  end
99
110
  describe "/ other" do
@@ -105,19 +116,20 @@ describe "Exchange::Currency" do
105
116
  end
106
117
  it "should be able to multiply by another currency value" do
107
118
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
119
+ Exchange.configuration.allow_mixed_operations = true
108
120
  (subject / Exchange::Currency.new(10, :chf)).value.round(2).should == BigDecimal.new("3.65")
109
121
  (subject / Exchange::Currency.new(23.3, :eur)).currency.should == :usd
110
122
  end
111
123
  it "should raise when currencies get mixed and the configuration does not allow it" do
112
- Exchange::Configuration.allow_mixed_operations = false
124
+ Exchange.configuration.allow_mixed_operations = false
113
125
  lambda { subject / Exchange::Currency.new(30, :chf) }.should raise_error(Exchange::CurrencyMixError)
114
- Exchange::Configuration.allow_mixed_operations = true
126
+ Exchange.configuration.allow_mixed_operations = true
115
127
  end
116
128
  it "should not raise when currencies get mixed and the configuration does not allow if the other currency is the same" do
117
- Exchange::Configuration.allow_mixed_operations = false
129
+ Exchange.configuration.allow_mixed_operations = false
118
130
  mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
119
131
  lambda { subject / Exchange::Currency.new(30, :usd) }.should_not raise_error
120
- Exchange::Configuration.allow_mixed_operations = true
132
+ Exchange.configuration.allow_mixed_operations = true
121
133
  end
122
134
  end
123
135
  describe "comparison" do
@@ -272,7 +284,7 @@ describe "Exchange::Currency" do
272
284
  5.eur(:at => Time.gm(2011,1,1)).to_usd.value.should == 5.eur.to_usd(:at => Time.gm(2011,1,1)).value
273
285
  end
274
286
  it "should raise errors for currency conversions it does not have rates for" do
275
- lambda { subject.to_ssp }.should raise_error(NoRateError)
287
+ lambda { subject.to_ssp }.should raise_error(Exchange::NoRateError)
276
288
  end
277
289
  it "should pass on methods it does not understand to its number" do
278
290
  subject.to_f.should == 40
@@ -3,7 +3,9 @@ require 'spec_helper'
3
3
  describe "Exchange::ExternalAPI::Base" do
4
4
  subject { Exchange::ExternalAPI::Base.new }
5
5
  before(:each) do
6
- Exchange::Configuration.cache = false
6
+ Exchange.configuration = Exchange::Configuration.new{|c|
7
+ c.cache = {:subclass => :no_cache}
8
+ }
7
9
  end
8
10
  before(:each) do
9
11
  subject.instance_variable_set("@rates", {'EUR' => BigDecimal.new("3.45"), 'CHF' => BigDecimal.new("5.565")})
@@ -2,7 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe "Exchange::ExternalAPI::Call" do
4
4
  before(:all) do
5
- Exchange::Configuration.cache = false
5
+ Exchange.configuration = Exchange::Configuration.new{|c|
6
+ c.cache = {
7
+ :subclass => :no_cache
8
+ }
9
+ }
6
10
  end
7
11
  describe "initialization" do
8
12
  context "with a json api" do
@@ -2,7 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe "Exchange::ExternalAPI::CurrencyBot" do
4
4
  before(:all) do
5
- Exchange::Configuration.cache = false
5
+ Exchange.configuration = Exchange::Configuration.new{|c|
6
+ c.cache = {
7
+ :subclass => :no_cache
8
+ }
9
+ }
6
10
  end
7
11
  describe "updating rates" do
8
12
  subject { Exchange::ExternalAPI::CurrencyBot.new }
@@ -1,15 +1,19 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Exchange::ExternalAPI::ECB" do
3
+ describe "Exchange::ExternalAPI::Ecb" do
4
4
  before(:all) do
5
- Exchange::Configuration.cache = false
5
+ Exchange.configuration = Exchange::Configuration.new { |c|
6
+ c.cache = {
7
+ :subclass => :no_cache
8
+ }
9
+ }
6
10
  end
7
11
  before(:each) do
8
12
  time = Time.gm(2012,2,3)
9
13
  Time.stub! :now => time
10
14
  end
11
15
  describe "updating rates" do
12
- subject { Exchange::ExternalAPI::ECB.new }
16
+ subject { Exchange::ExternalAPI::Ecb.new }
13
17
  before(:each) do
14
18
  mock_api("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml", fixture('api_responses/example_ecb_xml_90d.xml'))
15
19
  end
@@ -23,7 +27,7 @@ describe "Exchange::ExternalAPI::ECB" do
23
27
  end
24
28
  end
25
29
  describe "conversion" do
26
- subject { Exchange::ExternalAPI::ECB.new }
30
+ subject { Exchange::ExternalAPI::Ecb.new }
27
31
  before(:each) do
28
32
  mock_api("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml", fixture('api_responses/example_ecb_xml_90d.xml'))
29
33
  end
@@ -38,7 +42,7 @@ describe "Exchange::ExternalAPI::ECB" do
38
42
  end
39
43
  end
40
44
  describe "historic conversion" do
41
- subject { Exchange::ExternalAPI::ECB.new }
45
+ subject { Exchange::ExternalAPI::Ecb.new }
42
46
  before(:each) do
43
47
  mock_api("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml", fixture('api_responses/example_ecb_xml_history.xml'))
44
48
  end