exchange 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +54 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -12
- data/Gemfile.lock +14 -28
- data/README.rdoc +91 -68
- data/changelog.rdoc +20 -1
- data/exchange-0.7.5.gem +0 -0
- data/exchange-0.7.6.gem +0 -0
- data/exchange.gemspec +22 -102
- data/lib/core_extensions/conversability.rb +5 -3
- data/lib/exchange/base.rb +1 -1
- data/lib/exchange/cache/base.rb +11 -3
- data/lib/exchange/cache/memcached.rb +4 -3
- data/lib/exchange/configuration.rb +5 -14
- data/lib/exchange/currency.rb +69 -19
- data/lib/exchange/external_api/base.rb +75 -15
- data/lib/exchange/external_api/call.rb +7 -4
- data/lib/exchange/external_api/currency_bot.rb +12 -2
- data/lib/exchange/external_api/ecb.rb +62 -8
- data/lib/exchange/external_api/json.rb +3 -13
- data/lib/exchange/external_api/xavier_media.rb +51 -8
- data/lib/exchange/helper.rb +1 -1
- data/lib/exchange/iso_4217.rb +1 -0
- data/spec/exchange/cache/memcached_spec.rb +41 -15
- data/spec/exchange/currency_spec.rb +185 -7
- data/spec/exchange/external_api/call_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -1
- metadata +37 -31
- data/VERSION +0 -1
@@ -13,23 +13,37 @@ module Exchange
|
|
13
13
|
# # Define here which currencies your API can handle
|
14
14
|
# CURRENCIES = %W(usd chf)
|
15
15
|
#
|
16
|
-
# # Every instance of ExternalAPI Class has to have an update function which
|
16
|
+
# # Every instance of ExternalAPI Class has to have an update function which
|
17
|
+
# # gets the rates from the API
|
18
|
+
# #
|
17
19
|
# def update(opts={})
|
18
20
|
# # assure that you will get a Time object for the historical dates
|
19
|
-
#
|
21
|
+
# #
|
22
|
+
# time = helper.assure_time(opts[:at])
|
20
23
|
#
|
21
|
-
# #
|
24
|
+
# # Call your API (shown here with a helper function that builds your API URL).
|
25
|
+
# # Like this, your calls will get cached.
|
26
|
+
# #
|
22
27
|
# Call.new(api_url(time), :at => time) do |result|
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
28
|
+
#
|
29
|
+
# # Assign the currency conversion base.
|
30
|
+
# # Attention, this is readonly, self.base= won't work
|
31
|
+
# #
|
32
|
+
# @base = result['base']
|
33
|
+
#
|
34
|
+
# # assign the rates, this has to be a hash with the following format:
|
35
|
+
# # {'USD' => 1.23242, 'CHF' => 1.34323}.
|
36
|
+
# #
|
37
|
+
# # Attention, this is readonly, self.rates= won't work
|
38
|
+
# #
|
39
|
+
# @rates = result['rates']
|
40
|
+
#
|
41
|
+
# # Timestamp the api call result. This may come in handy to assure you have
|
42
|
+
# # the right result.
|
43
|
+
# #
|
44
|
+
# # Attention, this is readonly, self.timestamp= won't work
|
45
|
+
# #
|
46
|
+
# @timestamp = result['timestamp'].to_i
|
33
47
|
# end
|
34
48
|
#
|
35
49
|
# private
|
@@ -41,8 +55,11 @@ module Exchange
|
|
41
55
|
# end
|
42
56
|
# end
|
43
57
|
# end
|
58
|
+
#
|
44
59
|
# # Now, you can configure your API in the configuration. The Symbol will get camelcased and constantized
|
60
|
+
# #
|
45
61
|
# Exchange::Configuration.api.subclass = :my_custom
|
62
|
+
#
|
46
63
|
# # Have fun, and don't forget to write tests.
|
47
64
|
#
|
48
65
|
module ExternalAPI
|
@@ -69,6 +86,28 @@ module Exchange
|
|
69
86
|
#
|
70
87
|
attr_reader :rates
|
71
88
|
|
89
|
+
# @attr_reader
|
90
|
+
# @return [Exchange::Cache] The cache subclass
|
91
|
+
attr_reader :cache
|
92
|
+
|
93
|
+
# @attr_reader
|
94
|
+
# @return [Exchange::API] The api subclass
|
95
|
+
attr_reader :api
|
96
|
+
|
97
|
+
# @attr_reader
|
98
|
+
# @return [Exchange::Helper] The Exchange Helper
|
99
|
+
attr_reader :helper
|
100
|
+
|
101
|
+
# Initialize with a convenience accessor for the Cache and the api subclass
|
102
|
+
# @param [Any] args The args to initialize with
|
103
|
+
#
|
104
|
+
def initialize *args
|
105
|
+
@cache = Exchange.configuration.cache.subclass
|
106
|
+
@api = Exchange.configuration.api.subclass
|
107
|
+
@helper = Exchange::Helper.instance
|
108
|
+
|
109
|
+
super *args
|
110
|
+
end
|
72
111
|
|
73
112
|
# Delivers an exchange rate from one currency to another with the option of getting a historical exchange rate. This rate
|
74
113
|
# has to be multiplied with the amount of the currency which you define in from
|
@@ -82,13 +121,17 @@ module Exchange
|
|
82
121
|
# #=> 1.232231231
|
83
122
|
#
|
84
123
|
def rate(from, to, opts={})
|
85
|
-
rate =
|
124
|
+
rate = cache.cached(api, opts.merge(:key_for => [from, to], :plain => true)) do
|
86
125
|
update(opts)
|
126
|
+
|
87
127
|
rate_from = self.rates[to.to_s.upcase]
|
88
128
|
rate_to = self.rates[from.to_s.upcase]
|
89
|
-
|
129
|
+
|
130
|
+
test_for_rates_and_raise_if_nil rate_from, rate_to, opts[:at]
|
131
|
+
|
90
132
|
rate_from / rate_to
|
91
133
|
end
|
134
|
+
|
92
135
|
BigDecimal.new(rate.to_s)
|
93
136
|
end
|
94
137
|
|
@@ -107,6 +150,23 @@ module Exchange
|
|
107
150
|
amount * rate(from, to, opts)
|
108
151
|
end
|
109
152
|
|
153
|
+
# Converts an array to a hash
|
154
|
+
# @param [Array] array The array to convert
|
155
|
+
# @return [Hash] The hash out of the array
|
156
|
+
#
|
157
|
+
def to_hash! array
|
158
|
+
Hash[*array]
|
159
|
+
end
|
160
|
+
|
161
|
+
# Test for a error to be thrown when no rates are present
|
162
|
+
# @param [String] rate_from The rate from which should be converted
|
163
|
+
# @param [String] rate_to The rate to which should be converted
|
164
|
+
# @param [Time] time The time at which should be converted
|
165
|
+
# @raise [NoRateError] An error indicating that there is no rate present when there is no rate present
|
166
|
+
#
|
167
|
+
def test_for_rates_and_raise_if_nil rate_from, rate_to, time=nil
|
168
|
+
raise NoRateError.new("No rates where found for #{from} to #{to} #{'at ' + time.to_s if time}") unless rate_from && rate_to
|
169
|
+
end
|
110
170
|
end
|
111
171
|
end
|
112
172
|
end
|
@@ -30,14 +30,16 @@ module Exchange
|
|
30
30
|
# result = Exchange::ExternalAPI::Call.new('http://yourapiurl.com', :format => :xml)
|
31
31
|
# # Do something with that result
|
32
32
|
#
|
33
|
-
def initialize url, options={}, &block
|
33
|
+
def initialize url, options={}, &block
|
34
34
|
Exchange::GemLoader.new('nokogiri').try_load if options[:format] == :xml
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
api_config = Exchange.configuration.api
|
37
|
+
|
38
|
+
result = Exchange.configuration.cache.subclass.cached(options[:api] || api_config.subclass, options) do
|
39
|
+
load_url(url, options[:retries] || api_config.retries, options[:retry_with])
|
38
40
|
end
|
39
41
|
|
40
|
-
parsed = options[:format] == :xml ? Nokogiri.parse(result) : ::JSON.load(result)
|
42
|
+
parsed = options[:format] == :xml ? Nokogiri::XML.parse(result.sub("\n", '')) : ::JSON.load(result)
|
41
43
|
|
42
44
|
return parsed unless block_given?
|
43
45
|
yield parsed
|
@@ -49,6 +51,7 @@ module Exchange
|
|
49
51
|
# @param [String] url The url to be loaded
|
50
52
|
# @param [Integer] retries The number of retries to do if the API Call should fail with a HTTP Error
|
51
53
|
# @param [Array] retry_with An array of urls to retry the API call with if the call to the original URL should fail. These values will be shifted until a call succeeds or the number of maximum retries is reached
|
54
|
+
# @todo install a timeout for slow requests, but respect when loading large files
|
52
55
|
#
|
53
56
|
def load_url(url, retries, retry_with)
|
54
57
|
begin
|
@@ -24,17 +24,27 @@ module Exchange
|
|
24
24
|
# Exchange::ExternalAPI::CurrencyBot.new.update(:at => Time.gm(3,2,2010))
|
25
25
|
#
|
26
26
|
def update(opts={})
|
27
|
-
time =
|
27
|
+
time = helper.assure_time(opts[:at])
|
28
28
|
|
29
29
|
Call.new(api_url(time), :at => time) do |result|
|
30
30
|
@base = result['base']
|
31
|
-
@rates =
|
31
|
+
@rates = extract_rates(result)
|
32
32
|
@timestamp = result['timestamp'].to_i
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
+
# Helper method to extract rates from the api call result
|
39
|
+
# @param [JSON] parsed The parsed result
|
40
|
+
# @return [Hash] A hash with rates
|
41
|
+
# @since 0.7
|
42
|
+
# @version 0.7
|
43
|
+
#
|
44
|
+
def extract_rates parsed
|
45
|
+
to_hash! parsed['rates'].keys.zip(parsed['rates'].values.map{|v| BigDecimal.new(v.to_s) }).flatten
|
46
|
+
end
|
47
|
+
|
38
48
|
# A helper function to build an api url for either a specific time or the latest available rates
|
39
49
|
# @param [Time] time The time to build the api url for
|
40
50
|
# @return [String] an api url for the time specified
|
@@ -4,7 +4,7 @@ module Exchange
|
|
4
4
|
# The ECB class, handling communication with the European Central Bank XML File API
|
5
5
|
# You can find further information on the European Central Bank XML API API here: http://www.ecb.int/stats/exchange/eurofxref/html/index.en.html
|
6
6
|
# @author Beat Richartz
|
7
|
-
# @version 0.
|
7
|
+
# @version 0.7
|
8
8
|
# @since 0.3
|
9
9
|
#
|
10
10
|
class Ecb < XML
|
@@ -28,22 +28,23 @@ module Exchange
|
|
28
28
|
# @example Update the ecb API to use the file of March 2, 2010
|
29
29
|
# Exchange::ExternalAPI::Ecb.new.update(:at => Time.gm(3,2,2010))
|
30
30
|
#
|
31
|
+
# @since 0.1
|
32
|
+
# @version 0.7
|
33
|
+
#
|
31
34
|
def update(opts={})
|
32
|
-
time =
|
33
|
-
times =
|
35
|
+
time = helper.assure_time(opts[:at], :default => :now)
|
36
|
+
times = map_retry_times time
|
34
37
|
|
35
38
|
# Since the Ecb File retrieved can be very large (> 5MB for the history file) and parsing takes a fair amount of time,
|
36
39
|
# caching is doubled on this API
|
37
40
|
#
|
38
|
-
|
41
|
+
cache.cached(self.class, :at => time) do
|
39
42
|
Call.new(api_url(time), call_opts(time)) do |result|
|
40
43
|
t = time
|
41
44
|
|
42
45
|
# Weekends do not have rates present
|
43
46
|
#
|
44
|
-
while (r = result
|
45
|
-
t = times.shift
|
46
|
-
end
|
47
|
+
t = times.shift while (r = find_rate!(result, t)).empty? && !times.empty?
|
47
48
|
|
48
49
|
@callresult = r.to_s
|
49
50
|
end
|
@@ -52,7 +53,7 @@ module Exchange
|
|
52
53
|
parsed = Nokogiri.parse(self.callresult)
|
53
54
|
|
54
55
|
@base = 'EUR' # We just have to assume, since it's the ECB
|
55
|
-
@rates =
|
56
|
+
@rates = extract_rates(parsed.children.children)
|
56
57
|
@timestamp = time.to_i
|
57
58
|
end
|
58
59
|
|
@@ -72,9 +73,62 @@ module Exchange
|
|
72
73
|
].join('/')
|
73
74
|
end
|
74
75
|
|
76
|
+
# A helper method to find rates from the callresult given a certain time
|
77
|
+
# ECB packs the rates in «Cubes», so we try to find the cube appropriate to the time
|
78
|
+
# @param [Nokogiri::XML] parsed The parsed callresult
|
79
|
+
# @param [Time] time The time to parse for
|
80
|
+
# @return [Nokogiri::XML, NilClass] the rate, hopefully
|
81
|
+
# @since 0.7
|
82
|
+
# @version 0.7
|
83
|
+
#
|
84
|
+
def find_rate! parsed, time
|
85
|
+
parsed.css("Cube[time=\"#{time.strftime("%Y-%m-%d")}\"]")
|
86
|
+
end
|
87
|
+
|
88
|
+
# A helper method to extract rates from the callresult
|
89
|
+
# @param [Nokogiri::XML] parsed the parsed api data
|
90
|
+
# @return [Hash] a hash with rates
|
91
|
+
# @since 0.7
|
92
|
+
# @version 0.7
|
93
|
+
#
|
94
|
+
def extract_rates parsed
|
95
|
+
rate_array = parsed.map { |c|
|
96
|
+
map_to_currency_or_rate c
|
97
|
+
}.compact.flatten
|
98
|
+
|
99
|
+
to_hash!(['EUR', BigDecimal.new("1")] + rate_array)
|
100
|
+
end
|
101
|
+
|
102
|
+
# a helper method to map a key value pair to either currency or rate
|
103
|
+
# @param [Nokogiri::XML] xml a parsed xml part of the document
|
104
|
+
# @return [Array] An array with the following structure [currency, value, currency, value]
|
105
|
+
# @since 0.7
|
106
|
+
# @version 0.7
|
107
|
+
#
|
108
|
+
def map_to_currency_or_rate xml
|
109
|
+
unless (values = xml.attributes.values).empty?
|
110
|
+
values.map { |v|
|
111
|
+
val = v.value
|
112
|
+
val.match(/\d+/) ? BigDecimal.new(val) : val
|
113
|
+
}.sort_by(&:to_s).reverse
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Helper method to map retry times
|
118
|
+
# @param [Time] time The time to start with
|
119
|
+
# @return [Array] An array of times to retry api operation with
|
120
|
+
# @since 0.7
|
121
|
+
# @version 0.7
|
122
|
+
#
|
123
|
+
def map_retry_times time
|
124
|
+
Exchange.configuration.api.retries.times.map{ |i| time - 86400 * (i+1) }
|
125
|
+
end
|
126
|
+
|
75
127
|
# a wrapper for the call options, since the cache period is quite complex
|
76
128
|
# @param [Time] time The date of the exchange rate
|
77
129
|
# @return [Hash] a hash with the call options
|
130
|
+
# @since 0.6
|
131
|
+
# @version 0.6
|
78
132
|
#
|
79
133
|
def call_opts time
|
80
134
|
{:format => :xml, :at => time, :cache => :file, :cache_period => time >= Time.now - 90 * 86400 ? :daily : :monthly}
|
@@ -1,22 +1,12 @@
|
|
1
1
|
module Exchange
|
2
2
|
module ExternalAPI
|
3
3
|
|
4
|
-
# The json base class takes care of JSON apis.
|
5
|
-
#
|
4
|
+
# The json base class takes care of JSON apis.
|
5
|
+
# This may serve as a base for some operations which might be common to the json apis
|
6
6
|
# @author Beat Richartz
|
7
7
|
# @version 0.6
|
8
8
|
# @since 0.6
|
9
9
|
#
|
10
|
-
|
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
|
10
|
+
JSON = Class.new Base
|
21
11
|
end
|
22
12
|
end
|
@@ -4,7 +4,7 @@ module Exchange
|
|
4
4
|
# The XavierMedia API class, handling communication with the Xavier Media Currency API
|
5
5
|
# You can find further information on the Xaviermedia API here: http://www.xavierforum.com/viewtopic.php?f=5&t=10979&sid=671a685edbfa5dbec219fbc6793d5057
|
6
6
|
# @author Beat Richartz
|
7
|
-
# @version 0.
|
7
|
+
# @version 0.7
|
8
8
|
# @since 0.1
|
9
9
|
#
|
10
10
|
class XavierMedia < XML
|
@@ -16,21 +16,20 @@ module Exchange
|
|
16
16
|
|
17
17
|
# Updates the rates by getting the information from Xaviermedia API for today or a defined historical date
|
18
18
|
# The call gets cached for a maximum of 24 hours.
|
19
|
-
# @version 0.
|
19
|
+
# @version 0.7
|
20
20
|
# @param [Hash] opts Options to define for the API Call
|
21
21
|
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
22
22
|
# @example Update the currency bot API to use the file of March 2, 2010
|
23
23
|
# Exchange::ExternalAPI::XavierMedia.new.update(:at => Time.gm(3,2,2010))
|
24
24
|
#
|
25
25
|
def update(opts={})
|
26
|
-
time =
|
26
|
+
time = helper.assure_time(opts[:at], :default => :now)
|
27
27
|
api_url = api_url(time)
|
28
|
-
retry_urls = Exchange.configuration.api.retries.times.map{ |i| api_url(time - 86400 * (i+1)) }
|
29
28
|
|
30
|
-
Call.new(api_url, :
|
31
|
-
@base = result
|
32
|
-
@rates =
|
33
|
-
@timestamp =
|
29
|
+
Call.new(api_url, api_opts(opts.merge(:at => time))) do |result|
|
30
|
+
@base = extract_base_currency result
|
31
|
+
@rates = extract_rates result
|
32
|
+
@timestamp = extract_timestamp result
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
@@ -44,6 +43,50 @@ module Exchange
|
|
44
43
|
[API_URL, "#{time.strftime("%Y/%m/%d")}.xml"].join('/')
|
45
44
|
end
|
46
45
|
|
46
|
+
# Options for the API call to make
|
47
|
+
# @param [Hash] opts The options to generate the call options with
|
48
|
+
# @option opts [Time, String] :at a historical date to get the exchange rates for
|
49
|
+
# @return [Hash] The options hash for the API call
|
50
|
+
# @since 0.6
|
51
|
+
# @version 0.6
|
52
|
+
#
|
53
|
+
def api_opts(opts={})
|
54
|
+
retry_urls = Exchange.configuration.api.retries.times.map { |i| api_url(opts[:at] - 86400 * (i+1)) }
|
55
|
+
|
56
|
+
{ :format => :xml, :at => opts[:at], :retry_with => retry_urls }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Extract a timestamp of the callresult
|
60
|
+
# @param [Nokogiri::XML] result the callresult
|
61
|
+
# @return [Integer] A unix timestamp
|
62
|
+
# @since 0.7
|
63
|
+
# @version 0.7
|
64
|
+
#
|
65
|
+
def extract_timestamp(result)
|
66
|
+
Time.gm(*result.css('fx_date').children[0].to_s.split('-')).to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
# Extract rates from the callresult
|
70
|
+
# @param [Nokogiri::XML] result the callresult
|
71
|
+
# @return [Hash] A hash with currency / rate pairs
|
72
|
+
# @since 0.7
|
73
|
+
# @version 0.7
|
74
|
+
#
|
75
|
+
def extract_rates(result)
|
76
|
+
rates_array = result.css('fx currency_code').children.map(&:to_s).zip(result.css('fx rate').children.map{|c| BigDecimal.new(c.to_s) }).flatten
|
77
|
+
to_hash!(rates_array)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Extract the base currency from the callresult
|
81
|
+
# @param [Nokogiri::XML] result the callresult
|
82
|
+
# @return [String] The base currency for the rates
|
83
|
+
# @since 0.7
|
84
|
+
# @version 0.7
|
85
|
+
#
|
86
|
+
def extract_base_currency(result)
|
87
|
+
result.css('basecurrency').children[0].to_s
|
88
|
+
end
|
89
|
+
|
47
90
|
end
|
48
91
|
end
|
49
92
|
end
|
data/lib/exchange/helper.rb
CHANGED
@@ -13,7 +13,7 @@ module Exchange
|
|
13
13
|
extend SingleForwardable
|
14
14
|
|
15
15
|
# A helper function to assure a value is an instance of time
|
16
|
-
# @param [Time, String, NilClass] The value to be asserted
|
16
|
+
# @param [Time, String, NilClass] arg The value to be asserted
|
17
17
|
# @param [Hash] opts Options for assertion
|
18
18
|
# @option opts [Symbol] :default a method that can be sent to Time if the argument is nil (:now for example)
|
19
19
|
#
|
data/lib/exchange/iso_4217.rb
CHANGED
@@ -68,26 +68,52 @@ describe "Exchange::CacheDalli::Client" do
|
|
68
68
|
end
|
69
69
|
context "when no cached result exists" do
|
70
70
|
let(:client) { mock('memcached') }
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
71
|
+
context "when returning nil" do
|
72
|
+
before(:each) do
|
73
|
+
subject.should_receive(:key).with('API_CLASS', {}).twice.and_return('KEY')
|
74
|
+
client.should_receive(:get).with('KEY').and_return(nil)
|
75
|
+
end
|
76
|
+
context "with daily cache" do
|
77
|
+
it "should call the block and set and return the result" do
|
78
|
+
client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}", 86400).once
|
79
|
+
subject.cached('API_CLASS') { {'RESULT' => 'YAY'} }.should == {'RESULT' => 'YAY'}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
context "with hourly cache" do
|
83
|
+
before(:each) do
|
84
|
+
Exchange.configuration.cache.expire = :hourly
|
85
|
+
end
|
86
|
+
after(:each) do
|
87
|
+
Exchange.configuration.cache.expire = :daily
|
88
|
+
end
|
89
|
+
it "should call the block and set and return the result" do
|
90
|
+
client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}", 3600).once
|
91
|
+
subject.cached('API_CLASS') { {'RESULT' => 'YAY'} }.should == {'RESULT' => 'YAY'}
|
92
|
+
end
|
79
93
|
end
|
80
94
|
end
|
81
|
-
context "
|
95
|
+
context "when returning an empty string" do
|
82
96
|
before(:each) do
|
83
|
-
|
97
|
+
subject.should_receive(:key).with('API_CLASS', {}).twice.and_return('KEY')
|
98
|
+
client.should_receive(:get).with('KEY').and_return('')
|
84
99
|
end
|
85
|
-
|
86
|
-
|
100
|
+
context "with daily cache" do
|
101
|
+
it "should call the block and set and return the result" do
|
102
|
+
client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}", 86400).once
|
103
|
+
subject.cached('API_CLASS') { {'RESULT' => 'YAY'} }.should == {'RESULT' => 'YAY'}
|
104
|
+
end
|
87
105
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
106
|
+
context "with hourly cache" do
|
107
|
+
before(:each) do
|
108
|
+
Exchange.configuration.cache.expire = :hourly
|
109
|
+
end
|
110
|
+
after(:each) do
|
111
|
+
Exchange.configuration.cache.expire = :daily
|
112
|
+
end
|
113
|
+
it "should call the block and set and return the result" do
|
114
|
+
client.should_receive(:set).with('KEY', "{\"RESULT\":\"YAY\"}", 3600).once
|
115
|
+
subject.cached('API_CLASS') { {'RESULT' => 'YAY'} }.should == {'RESULT' => 'YAY'}
|
116
|
+
end
|
91
117
|
end
|
92
118
|
end
|
93
119
|
end
|