exchange 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -8
- data/Gemfile.lock +12 -6
- data/LICENSE.txt +1 -1
- data/README.rdoc +15 -12
- data/VERSION +1 -1
- data/exchange.gemspec +13 -23
- data/lib/core_extensions/conversability.rb +5 -4
- data/lib/exchange.rb +6 -11
- data/lib/exchange/base.rb +19 -0
- data/lib/exchange/cache/base.rb +57 -32
- data/lib/exchange/cache/file.rb +41 -45
- data/lib/exchange/cache/memcached.rb +28 -29
- data/lib/exchange/cache/no_cache.rb +4 -24
- data/lib/exchange/cache/rails.rb +26 -26
- data/lib/exchange/cache/redis.rb +30 -33
- data/lib/exchange/configuration.rb +167 -73
- data/lib/exchange/currency.rb +61 -30
- data/lib/exchange/external_api.rb +2 -0
- data/lib/exchange/external_api/base.rb +11 -5
- data/lib/exchange/external_api/call.rb +10 -7
- data/lib/exchange/external_api/currency_bot.rb +9 -5
- data/lib/exchange/external_api/ecb.rb +29 -13
- data/lib/exchange/external_api/json.rb +22 -0
- data/lib/exchange/external_api/xavier_media.rb +6 -5
- data/lib/exchange/external_api/xml.rb +22 -0
- data/lib/exchange/gem_loader.rb +35 -0
- data/lib/exchange/helper.rb +22 -15
- data/lib/exchange/iso_4217.rb +55 -44
- data/spec/core_extensions/conversability_spec.rb +2 -2
- data/spec/exchange/cache/base_spec.rb +7 -7
- data/spec/exchange/cache/file_spec.rb +19 -18
- data/spec/exchange/cache/memcached_spec.rb +29 -26
- data/spec/exchange/cache/no_cache_spec.rb +12 -6
- data/spec/exchange/cache/rails_spec.rb +13 -9
- data/spec/exchange/cache/redis_spec.rb +24 -24
- data/spec/exchange/configuration_spec.rb +38 -30
- data/spec/exchange/currency_spec.rb +33 -21
- data/spec/exchange/external_api/base_spec.rb +3 -1
- data/spec/exchange/external_api/call_spec.rb +5 -1
- data/spec/exchange/external_api/currency_bot_spec.rb +5 -1
- data/spec/exchange/external_api/ecb_spec.rb +9 -5
- data/spec/exchange/external_api/xavier_media_spec.rb +5 -1
- data/spec/exchange/gem_loader_spec.rb +29 -0
- data/spec/spec_helper.rb +1 -1
- metadata +12 -87
@@ -0,0 +1,22 @@
|
|
1
|
+
module Exchange
|
2
|
+
module ExternalAPI
|
3
|
+
|
4
|
+
# The json base class takes care of JSON apis. It assumes you would want to use json as a parser and preloads the gem
|
5
|
+
# Also, this may serve as a base for some operations which might be common to the json apis
|
6
|
+
# @author Beat Richartz
|
7
|
+
# @version 0.6
|
8
|
+
# @since 0.6
|
9
|
+
#
|
10
|
+
class JSON < Base
|
11
|
+
|
12
|
+
# Initializer, essentially takes the arguments passed to initialization, loads the json gem on the way
|
13
|
+
# and passes the arguments to the api base
|
14
|
+
#
|
15
|
+
def initialize *args
|
16
|
+
Exchange::GemLoader.new('json').try_load unless defined?(::JSON)
|
17
|
+
super *args
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -6,8 +6,9 @@ module Exchange
|
|
6
6
|
# @author Beat Richartz
|
7
7
|
# @version 0.1
|
8
8
|
# @since 0.1
|
9
|
-
|
10
|
-
class XavierMedia <
|
9
|
+
#
|
10
|
+
class XavierMedia < XML
|
11
|
+
|
11
12
|
# The base of the Xaviermedia API URL
|
12
13
|
API_URL = "http://api.finance.xaviermedia.com/api"
|
13
14
|
# The currencies the Xaviermedia API URL can handle
|
@@ -20,11 +21,11 @@ module Exchange
|
|
20
21
|
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
21
22
|
# @example Update the currency bot API to use the file of March 2, 2010
|
22
23
|
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
23
|
-
|
24
|
+
#
|
24
25
|
def update(opts={})
|
25
26
|
time = Exchange::Helper.assure_time(opts[:at], :default => :now)
|
26
27
|
api_url = api_url(time)
|
27
|
-
retry_urls = Exchange
|
28
|
+
retry_urls = Exchange.configuration.api.retries.times.map{ |i| api_url(time - 86400 * (i+1)) }
|
28
29
|
|
29
30
|
Call.new(api_url, :format => :xml, :at => time, :retry_with => retry_urls) do |result|
|
30
31
|
@base = result.css('basecurrency').children[0].to_s
|
@@ -38,7 +39,7 @@ module Exchange
|
|
38
39
|
# A helper function which build a valid api url for the specified time
|
39
40
|
# @param [Time] time The exchange rate date for which the URL should be built
|
40
41
|
# @return [String] An Xaviermedia API URL for the specified time
|
41
|
-
|
42
|
+
#
|
42
43
|
def api_url(time)
|
43
44
|
[API_URL, "#{time.strftime("%Y/%m/%d")}.xml"].join('/')
|
44
45
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Exchange
|
2
|
+
module ExternalAPI
|
3
|
+
|
4
|
+
# The xml base class takes care of XML apis. It assumes you would want to use nokogiri as a parser and preloads the gem
|
5
|
+
# Also, this may serve as a base for some operations which might be common to the xml apis
|
6
|
+
# @author Beat Richartz
|
7
|
+
# @version 0.6
|
8
|
+
# @since 0.6
|
9
|
+
#
|
10
|
+
class XML < Base
|
11
|
+
|
12
|
+
# Initializer, essentially takes the arguments passed to initialization, loads nokogiri on the way
|
13
|
+
# and passes the arguments to the api base
|
14
|
+
#
|
15
|
+
def initialize *args
|
16
|
+
Exchange::GemLoader.new('nokogiri').try_load unless defined?(Nokogiri)
|
17
|
+
super *args
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Exchange
|
2
|
+
|
3
|
+
# The gem loader takes care of loading gems without adding too many unnecessary dependencies to the gem
|
4
|
+
# @author Beat Richartz
|
5
|
+
# @version 0.6
|
6
|
+
# @since 0.6
|
7
|
+
#
|
8
|
+
class GemLoader
|
9
|
+
|
10
|
+
# The error that gets thrown if a needed gem is not available or loadable
|
11
|
+
#
|
12
|
+
GemNotFoundError = Class.new LoadError
|
13
|
+
|
14
|
+
# initialize the loader with a gem name.
|
15
|
+
# @param [string] gem The gem to require
|
16
|
+
# @return [Exchange::Gemloader] an instance of the gemloader
|
17
|
+
# @example initialize a loader for the nokogiri gem
|
18
|
+
# Exchange::GemLoader.new('nokogiri')
|
19
|
+
#
|
20
|
+
def initialize gem
|
21
|
+
@gem = gem
|
22
|
+
end
|
23
|
+
|
24
|
+
# Try to require the gem specified on initialization.
|
25
|
+
# @raise [GemNotFoundError] an error indicating that the gem could not be found rather than a load error
|
26
|
+
# @example Try to load the JSON gem
|
27
|
+
# Exchange::GemLoader.new('json').try_load
|
28
|
+
#
|
29
|
+
def try_load
|
30
|
+
require @gem
|
31
|
+
rescue LoadError => e
|
32
|
+
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
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/exchange/helper.rb
CHANGED
@@ -1,27 +1,34 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
1
4
|
module Exchange
|
5
|
+
|
2
6
|
# Helper Functions that get used throughout the gem can be placed here
|
3
7
|
# @author Beat Richartz
|
4
|
-
# @version 0.
|
8
|
+
# @version 0.6
|
5
9
|
# @since 0.3
|
6
|
-
|
10
|
+
#
|
7
11
|
class Helper
|
8
|
-
|
12
|
+
include Singleton
|
13
|
+
extend SingleForwardable
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
15
|
+
# A helper function to assure a value is an instance of time
|
16
|
+
# @param [Time, String, NilClass] The value to be asserted
|
17
|
+
# @param [Hash] opts Options for assertion
|
18
|
+
# @option opts [Symbol] :default a method that can be sent to Time if the argument is nil (:now for example)
|
19
|
+
#
|
20
|
+
def assure_time(arg=nil, opts={})
|
21
|
+
if arg
|
22
|
+
arg.kind_of?(Time) ? arg : Time.gm(*arg.split('-'))
|
23
|
+
elsif opts[:default]
|
24
|
+
Time.send(opts[:default])
|
21
25
|
end
|
22
|
-
|
23
26
|
end
|
24
27
|
|
28
|
+
# Forwards the assure_time method to the instance using singleforwardable
|
29
|
+
#
|
30
|
+
def_delegator :instance, :assure_time
|
31
|
+
|
25
32
|
end
|
26
33
|
|
27
34
|
end
|
data/lib/exchange/iso_4217.rb
CHANGED
@@ -1,67 +1,74 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
1
4
|
module Exchange
|
2
5
|
|
3
6
|
# This class handles everything that has to do with certified formatting of the different currencies. The standard is stored in
|
4
7
|
# the iso4217 YAML file.
|
5
|
-
# @version 0.
|
8
|
+
# @version 0.6
|
6
9
|
# @since 0.3
|
7
10
|
# @author Beat Richartz
|
8
|
-
|
11
|
+
#
|
9
12
|
class ISO4217
|
13
|
+
include Singleton
|
14
|
+
extend SingleForwardable
|
15
|
+
|
10
16
|
class << self
|
11
17
|
|
12
|
-
# The ISO 4217 that have to be loaded. Nothing much to say here. Just use this method to get to the definitions
|
13
|
-
# They are static, so they can be stored in a class variable without many worries
|
14
|
-
# @return [Hash] The iso427 Definitions with the currency code as keys
|
15
|
-
|
16
|
-
def definitions
|
17
|
-
@@definitions ||= YAML.load_file(File.join(EXCHANGE_GEM_ROOT_PATH, 'iso4217.yml'))
|
18
|
-
end
|
19
|
-
|
20
|
-
# Use this to instantiate a currency amount. For one, it is important that we use BigDecimal here so nothing gets lost because
|
21
|
-
# of floating point errors. For the other, This allows us to set the precision exactly according to the iso definition
|
22
|
-
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to instantiate
|
23
|
-
# @param [String, Symbol] currency The currency you want to instantiate the money in
|
24
|
-
# @return [BigDecimal] The instantiated currency
|
25
|
-
# @example instantiate a currency from a string
|
26
|
-
# Exchange::ISO4217.instantiate("4523", "usd") #=> #<Bigdecimal 4523.00>
|
27
|
-
|
28
|
-
def instantiate(amount, currency)
|
29
|
-
BigDecimal.new(amount.to_s, definitions[currency.to_s.upcase]['minor_unit'])
|
30
|
-
end
|
31
|
-
|
32
|
-
# Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
|
33
|
-
# with no worries how to display the currency.
|
34
|
-
# @param [BigDecimal, Fixed, Float] amount The amount of currency you want to stringify
|
35
|
-
# @param [String, Symbol] currency The currency you want to stringify
|
36
|
-
# @param [Hash] opts The options for formatting
|
37
|
-
# @option opts [Boolean] :amount_only Whether you want to have the currency in the string or not
|
38
|
-
# @return [String] The formatted string
|
39
|
-
# @example Convert a currency to a string
|
40
|
-
# Exchange::ISO4217.stringify(49.567, :usd) #=> "USD 49.57"
|
41
|
-
# @example Convert a currency without minor to a string
|
42
|
-
# Exchange::ISO4217.stringif(45, :jpy) #=> "JPY 45"
|
43
|
-
# @example Convert a currency with a three decimal minor to a string
|
44
|
-
# Exchange::ISO4217.stringif(34.34, :omr) #=> "OMR 34.340"
|
45
|
-
# @example Convert a currency to a string without the currency
|
46
|
-
# Exchange::ISO4217.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
|
47
|
-
|
48
|
-
def stringify(amount, currency, opts={})
|
49
|
-
format = "%.#{definitions[currency.to_s.upcase]['minor_unit']}f"
|
50
|
-
"#{currency.to_s.upcase + ' ' unless opts[:amount_only]}#{format % amount}"
|
51
|
-
end
|
52
|
-
|
53
18
|
private
|
54
19
|
# @private
|
55
20
|
# @macro [attach] install_operations
|
56
21
|
|
57
22
|
def install_operation op
|
58
23
|
self.class_eval <<-EOV
|
59
|
-
def
|
24
|
+
def #{op}(amount, currency, precision=nil)
|
60
25
|
minor = definitions[currency.to_s.upcase]['minor_unit']
|
61
26
|
(amount.is_a?(BigDecimal) ? amount : BigDecimal.new(amount.to_s, minor)).#{op}(precision || minor)
|
62
27
|
end
|
63
28
|
EOV
|
64
29
|
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# The ISO 4217 that have to be loaded. Nothing much to say here. Just use this method to get to the definitions
|
34
|
+
# They are static, so they can be stored in a class variable without many worries
|
35
|
+
# @return [Hash] The iso427 Definitions with the currency code as keys
|
36
|
+
#
|
37
|
+
def definitions
|
38
|
+
@@definitions ||= YAML.load_file(File.join(ROOT_PATH, 'iso4217.yml'))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Use this to instantiate a currency amount. For one, it is important that we use BigDecimal here so nothing gets lost because
|
42
|
+
# of floating point errors. For the other, This allows us to set the precision exactly according to the iso definition
|
43
|
+
# @param [BigDecimal, Fixed, Float, String] amount The amount of money you want to instantiate
|
44
|
+
# @param [String, Symbol] currency The currency you want to instantiate the money in
|
45
|
+
# @return [BigDecimal] The instantiated currency
|
46
|
+
# @example instantiate a currency from a string
|
47
|
+
# Exchange::ISO4217.instantiate("4523", "usd") #=> #<Bigdecimal 4523.00>
|
48
|
+
#
|
49
|
+
def instantiate(amount, currency)
|
50
|
+
BigDecimal.new(amount.to_s, definitions[currency.to_s.upcase]['minor_unit'])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Converts the currency to a string in ISO 4217 standardized format, either with or without the currency. This leaves you
|
54
|
+
# with no worries how to display the currency.
|
55
|
+
# @param [BigDecimal, Fixed, Float] amount The amount of currency you want to stringify
|
56
|
+
# @param [String, Symbol] currency The currency you want to stringify
|
57
|
+
# @param [Hash] opts The options for formatting
|
58
|
+
# @option opts [Boolean] :amount_only Whether you want to have the currency in the string or not
|
59
|
+
# @return [String] The formatted string
|
60
|
+
# @example Convert a currency to a string
|
61
|
+
# Exchange::ISO4217.stringify(49.567, :usd) #=> "USD 49.57"
|
62
|
+
# @example Convert a currency without minor to a string
|
63
|
+
# Exchange::ISO4217.stringif(45, :jpy) #=> "JPY 45"
|
64
|
+
# @example Convert a currency with a three decimal minor to a string
|
65
|
+
# Exchange::ISO4217.stringif(34.34, :omr) #=> "OMR 34.340"
|
66
|
+
# @example Convert a currency to a string without the currency
|
67
|
+
# Exchange::ISO4217.stringif(34.34, :omr, :amount_only => true) #=> "34.340"
|
68
|
+
#
|
69
|
+
def stringify(amount, currency, opts={})
|
70
|
+
format = "%.#{definitions[currency.to_s.upcase]['minor_unit']}f"
|
71
|
+
"#{currency.to_s.upcase + ' ' unless opts[:amount_only]}#{format % amount}"
|
65
72
|
end
|
66
73
|
|
67
74
|
# Use this to round a currency amount. This allows us to round exactly to the number of minors the currency has in the
|
@@ -91,5 +98,9 @@ module Exchange
|
|
91
98
|
|
92
99
|
install_operation :floor
|
93
100
|
|
101
|
+
# Forwards the assure_time method to the instance using singleforwardable
|
102
|
+
#
|
103
|
+
def_delegators :instance, :definitions, :instantiate, :stringify, :round, :ceil, :floor
|
104
|
+
|
94
105
|
end
|
95
106
|
end
|
@@ -2,14 +2,14 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "Exchange::Conversability" do
|
4
4
|
before(:all) do
|
5
|
-
Exchange::Configuration.cache =
|
5
|
+
Exchange.configuration = Exchange::Configuration.new { |c| c.cache = { :subclass => :no_cache } }
|
6
6
|
end
|
7
7
|
before(:each) do
|
8
8
|
@time = Time.gm(2012,8,27)
|
9
9
|
Time.stub! :now => @time
|
10
10
|
end
|
11
11
|
after(:all) do
|
12
|
-
Exchange::Configuration.cache = :memcached
|
12
|
+
Exchange::configuration = Exchange::Configuration.new { |c| c.cache = { :subclass => :memcached } }
|
13
13
|
end
|
14
14
|
it "should define all currencies on Fixnum, Float and BigDecimal" do
|
15
15
|
Exchange::ISO4217.definitions.keys.each do |c|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Exchange::Cache::Base" do
|
4
|
-
subject { Exchange::Cache::Base }
|
4
|
+
subject { Exchange::Cache::Base.instance }
|
5
5
|
describe "key generation" do
|
6
6
|
before(:each) do
|
7
7
|
time = Time.gm 2012, 03, 01, 23, 23, 23
|
@@ -9,20 +9,20 @@ describe "Exchange::Cache::Base" do
|
|
9
9
|
end
|
10
10
|
context "with a daily cache" do
|
11
11
|
it "should build a timestamped key with the class given, the yearday and the year" do
|
12
|
-
|
13
|
-
|
12
|
+
subject.send(:key, :xavier_media).should == 'exchange_xavier_media_2012_61'
|
13
|
+
subject.send(:key, :currency_bot).should == 'exchange_currency_bot_2012_61'
|
14
14
|
end
|
15
15
|
end
|
16
16
|
context "with an hourly cache" do
|
17
17
|
before(:each) do
|
18
|
-
Exchange
|
18
|
+
Exchange.configuration.cache.expire = :hourly
|
19
19
|
end
|
20
20
|
after(:each) do
|
21
|
-
Exchange
|
21
|
+
Exchange.configuration.cache.expire = :daily
|
22
22
|
end
|
23
23
|
it "should build a timestamped key with the class given, the yearday, the year and the hour" do
|
24
|
-
|
25
|
-
|
24
|
+
subject.send(:key, :xavier_media).should == 'exchange_xavier_media_2012_61_23'
|
25
|
+
subject.send(:key, :currency_bot).should == 'exchange_currency_bot_2012_61_23'
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Exchange::Cache::File" do
|
4
|
-
subject { Exchange::Cache::File }
|
4
|
+
subject { Exchange::Cache::File.instance }
|
5
5
|
before(:each) do
|
6
|
-
Exchange::Configuration.
|
7
|
-
c.cache =
|
8
|
-
|
6
|
+
Exchange.configuration = Exchange::Configuration.new { |c|
|
7
|
+
c.cache = {
|
8
|
+
:subclass => :file,
|
9
|
+
:path => 'STORE/PATH'
|
10
|
+
}
|
11
|
+
}
|
9
12
|
end
|
10
13
|
after(:each) do
|
11
|
-
Exchange::Configuration.
|
12
|
-
c.cache =
|
13
|
-
|
14
|
+
Exchange.configuration = Exchange::Configuration.new { |c|
|
15
|
+
c.cache = {
|
16
|
+
:subclass => :memcached
|
17
|
+
}
|
18
|
+
}
|
14
19
|
end
|
15
20
|
describe "cached" do
|
16
21
|
it "should raise an error if no block was given" do
|
@@ -20,9 +25,8 @@ describe "Exchange::Cache::File" do
|
|
20
25
|
context "with a daily cache" do
|
21
26
|
before(:each) do
|
22
27
|
subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
|
23
|
-
|
24
|
-
::File.should_receive(:
|
25
|
-
::File.should_receive(:read).with('STORE/KEY').and_return 'CONTENT'
|
28
|
+
::File.should_receive(:exists?).with('STORE/PATH/KEY').and_return(true)
|
29
|
+
::File.should_receive(:read).with('STORE/PATH/KEY').and_return 'CONTENT'
|
26
30
|
end
|
27
31
|
it "should return the file contents" do
|
28
32
|
subject.cached('API_CLASS') { 'something' }.should == 'CONTENT'
|
@@ -31,9 +35,8 @@ describe "Exchange::Cache::File" do
|
|
31
35
|
context "with an monthly cache" do
|
32
36
|
before(:each) do
|
33
37
|
subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).and_return('KEY')
|
34
|
-
|
35
|
-
::File.should_receive(:
|
36
|
-
::File.should_receive(:read).with('STORE/KEY').and_return 'CONTENT'
|
38
|
+
::File.should_receive(:exists?).with('STORE/PATH/KEY').and_return(true)
|
39
|
+
::File.should_receive(:read).with('STORE/PATH/KEY').and_return 'CONTENT'
|
37
40
|
end
|
38
41
|
it "should return the file contents" do
|
39
42
|
subject.cached('API_CLASS', :cache_period => :monthly) { 'something' }.should == 'CONTENT'
|
@@ -43,9 +46,8 @@ describe "Exchange::Cache::File" do
|
|
43
46
|
context "when no file is cached yet" do
|
44
47
|
before(:each) do
|
45
48
|
subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).at_most(3).times.and_return('KEY')
|
46
|
-
|
47
|
-
|
48
|
-
Dir.should_receive(:entries).with('STORE').once.and_return(%W(entry entry2))
|
49
|
+
::File.should_receive(:exists?).with('STORE/PATH/KEY').and_return(false)
|
50
|
+
Dir.should_receive(:entries).with('STORE/PATH').once.and_return(%W(entry entry2))
|
49
51
|
::File.should_receive(:delete).with(an_instance_of(String)).twice
|
50
52
|
FileUtils.should_receive(:mkdir_p).once
|
51
53
|
::File.should_receive(:open).once
|
@@ -57,8 +59,7 @@ describe "Exchange::Cache::File" do
|
|
57
59
|
context "when no result is returned" do
|
58
60
|
before(:each) do
|
59
61
|
subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).at_most(3).times.and_return('KEY')
|
60
|
-
|
61
|
-
::File.should_receive(:exists?).with('STORE/KEY').and_return(false)
|
62
|
+
::File.should_receive(:exists?).with('STORE/PATH/KEY').and_return(false)
|
62
63
|
FileUtils.should_receive(:mkdir_p).never
|
63
64
|
::File.should_receive(:open).never
|
64
65
|
end
|
@@ -1,41 +1,48 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "Exchange::
|
4
|
-
subject { Exchange::Cache::Memcached }
|
3
|
+
describe "Exchange::CacheDalli::Client" do
|
4
|
+
subject { Exchange::Cache::Memcached.instance }
|
5
5
|
before(:each) do
|
6
|
-
Exchange::Configuration.
|
7
|
-
c.
|
8
|
-
|
9
|
-
|
6
|
+
Exchange.configuration = Exchange::Configuration.new { |c|
|
7
|
+
c.cache = {
|
8
|
+
:host => 'HOST',
|
9
|
+
:port => 'PORT'
|
10
|
+
}
|
11
|
+
}
|
10
12
|
end
|
11
13
|
after(:each) do
|
12
|
-
Exchange::Configuration.
|
13
|
-
c.
|
14
|
-
|
15
|
-
|
14
|
+
Exchange.configuration = Exchange::Configuration.new { |c|
|
15
|
+
c.cache = {
|
16
|
+
:host => 'localhost',
|
17
|
+
:port => 11211
|
18
|
+
}
|
19
|
+
}
|
16
20
|
end
|
17
21
|
describe "client" do
|
18
22
|
let(:client) { mock('memcached') }
|
23
|
+
before(:each) do
|
24
|
+
Exchange::GemLoader.new('dalli').try_load
|
25
|
+
end
|
19
26
|
after(:each) do
|
20
|
-
subject.
|
27
|
+
subject.instance_variable_set "@client", nil
|
21
28
|
end
|
22
29
|
it "should set up a client on the specified host and port for the cache" do
|
23
|
-
::
|
30
|
+
Dalli::Client.should_receive(:new).with("HOST:PORT").and_return(client)
|
24
31
|
subject.client.should == client
|
25
32
|
end
|
26
33
|
end
|
27
34
|
describe "cached" do
|
35
|
+
let(:client) { mock('memcached', :get => '') }
|
36
|
+
before(:each) do
|
37
|
+
Dalli::Client.should_receive(:new).with("HOST:PORT").and_return(client)
|
38
|
+
end
|
39
|
+
after(:each) do
|
40
|
+
subject.instance_variable_set "@client", nil
|
41
|
+
end
|
28
42
|
it "should raise an error if no block was given" do
|
29
43
|
lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
|
30
44
|
end
|
31
45
|
context "when a cached result exists" do
|
32
|
-
let(:client) { mock('memcached') }
|
33
|
-
before(:each) do
|
34
|
-
::Memcached.should_receive(:new).with("HOST:PORT").and_return(client)
|
35
|
-
end
|
36
|
-
after(:each) do
|
37
|
-
subject.send(:remove_class_variable, "@@client")
|
38
|
-
end
|
39
46
|
context "when loading json" do
|
40
47
|
before(:each) do
|
41
48
|
subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
|
@@ -63,11 +70,7 @@ describe "Exchange::Cache::Memcached" do
|
|
63
70
|
let(:client) { mock('memcached') }
|
64
71
|
before(:each) do
|
65
72
|
subject.should_receive(:key).with('API_CLASS', {}).twice.and_return('KEY')
|
66
|
-
|
67
|
-
client.should_receive(:get).with('KEY').and_raise(::Memcached::NotFound)
|
68
|
-
end
|
69
|
-
after(:each) do
|
70
|
-
subject.send(:remove_class_variable, "@@client")
|
73
|
+
client.should_receive(:get).with('KEY').and_return(nil)
|
71
74
|
end
|
72
75
|
context "with daily cache" do
|
73
76
|
it "should call the block and set and return the result" do
|
@@ -77,10 +80,10 @@ describe "Exchange::Cache::Memcached" do
|
|
77
80
|
end
|
78
81
|
context "with hourly cache" do
|
79
82
|
before(:each) do
|
80
|
-
Exchange
|
83
|
+
Exchange.configuration.cache.expire = :hourly
|
81
84
|
end
|
82
85
|
after(:each) do
|
83
|
-
Exchange
|
86
|
+
Exchange.configuration.cache.expire = :daily
|
84
87
|
end
|
85
88
|
it "should call the block and set and return the result" do
|
86
89
|
client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}", 3600).once
|