/
api_cache.rb
140 lines (125 loc) · 4.3 KB
/
api_cache.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
require 'logger'
# Contains the complete public API for APICache.
#
# See APICache.get method and the README file.
#
# Before using APICache you should set the store to use using
# APICache.store=. For convenience, when no store is set an in memory store
# will be used (and a warning will be logged).
#
class APICache
class APICacheError < RuntimeError; end
class NotAvailableError < APICacheError; end
class TimeoutError < NotAvailableError; end
class InvalidResponse < NotAvailableError; end
class CannotFetch < NotAvailableError; end
class << self
attr_accessor :logger
attr_accessor :store
def logger # :nodoc:
@logger ||= begin
log = Logger.new(STDOUT)
log.level = Logger::INFO
log
end
end
# Set the logger to use. If not set, <tt>Logger.new(STDOUT)</tt> will be
# used.
#
def logger=(logger)
@logger = logger
end
def store # :nodoc:
@store ||= begin
APICache.logger.warn("Using in memory store")
APICache::MemoryStore.new
end
end
# Set the cache store to use. This should either be an instance of a
# moneta store or a subclass of APICache::AbstractStore. Moneta is
# recommended.
#
def store=(store)
@store = begin
if store.class < APICache::AbstractStore
store
elsif store.class.to_s =~ /Moneta/
MonetaStore.new(store)
elsif store.nil?
nil
else
raise ArgumentError, "Please supply an instance of either a moneta store or a subclass of APICache::AbstractStore"
end
end
end
end
# Optionally call with a block. The value of the block is then used to
# set the cache rather than calling the url. Use it for example if you need
# to make another type of request, catch custom error codes etc. To signal
# that the call failed just raise any exception - the value will then
# not be cached and the api will not be called again for options[:timeout]
# seconds. If an old value is available in the cache then it will be
# returned.
#
# An exception will be raised if the API cannot be fetched and the request
# cannot be served by the cache. This will either be a subclass of
# APICache::Error or an exception raised by the provided block.
#
# For example:
# APICache.get("http://twitter.com/statuses/user_timeline/6869822.atom")
#
# APICache.get \
# "http://twitter.com/statuses/user_timeline/6869822.atom",
# :cache => 60, :valid => 600
#
def self.get(key, options = {}, &block)
options = {
:cache => 600, # 10 minutes After this time fetch new data
:valid => 86400, # 1 day Maximum time to use old data
# :forever is a valid option
:period => 60, # 1 minute Maximum frequency to call API
:timeout => 5 # 5 seconds API response timeout
}.merge(options)
cache = APICache::Cache.new(key, {
:cache => options[:cache],
:valid => options[:valid]
})
api = APICache::API.new(key, {
:period => options[:period],
:timeout => options[:timeout]
}, &block)
cache_state = cache.state
if cache_state == :current
APICache.logger.debug "APICache #{@key}: Returning from cache"
cache.get
else
begin
value = api.get
cache.set(value)
value
rescue => e
APICache.logger.info "APICache #{key}: Exception raised (#{e.message} - #{e.class})" \
# No point outputting backgraces for internal APICache errors
APICache.logger.debug "Backtrace:\n#{e.backtrace.join("\n")}" unless e.kind_of?(APICacheError)
if cache_state == :refetch
cache.get
else
APICache.logger.warn "APICache #{key}: Data not available in the cache or from API"
if options.has_key?(:fail)
fail = options[:fail]
fail.respond_to?(:call) ? fail.call : fail
else
raise e
end
end
end
end
end
end
require 'api_cache/cache'
require 'api_cache/api'
APICache.autoload 'AbstractStore', 'api_cache/abstract_store'
APICache.autoload 'DalliStore', 'api_cache/dalli_store'
APICache.autoload 'MemoryStore', 'api_cache/memory_store'
APICache.autoload 'MonetaStore', 'api_cache/moneta_store'
APICache.autoload 'NullStore', 'api_cache/null_store'