exchange 0.5.1 → 0.6.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 (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