lib/api_cache/api.rb in api_cache-0.1.2 vs lib/api_cache/api.rb in api_cache-0.2.0
- old
+ new
@@ -1,70 +1,97 @@
require 'net/http'
-# This class wraps up querying the API and remembers when each API was
-# last queried in case there is a limit to the number that can be made.
-class APICache::API
- def initialize
- @query_times = {}
- end
-
- # Checks whether the API can be queried (i.e. whether retry_time has passed
- # since the last query to the API).
- #
- # If retry_time is 0 then there is no limit on how frequently queries can
- # be made to the API.
- def queryable?(key, retry_time)
- if @query_times[key]
- if Time.now - @query_times[key] > retry_time
- APICache.logger.log "Queryable: true - retry_time has passed"
- true
+class APICache
+ # Wraps up querying the API.
+ #
+ # Ensures that the API is not called more frequently than every +period+
+ # seconds, and times out API requests after +timeout+ seconds.
+ #
+ class API
+ # Takes the following options
+ #
+ # period:: Maximum frequency to call the API. If set to 0 then there is no
+ # limit on how frequently queries can be made to the API.
+ # timeout:: Timeout when calling api (either to the proviced url or
+ # excecuting the passed block)
+ # block:: If passed then the block is excecuted instead of HTTP GET
+ # against the provided key
+ #
+ def initialize(key, options, &block)
+ @key, @block = key, block
+ @timeout = options[:timeout]
+ @period = options[:period]
+ end
+
+ # Fetch data from the API.
+ #
+ # If no block is given then the key is assumed to be a URL and which will
+ # be queried expecting a 200 response. Otherwise the return value of the
+ # block will be used.
+ #
+ # This method can raise Timeout::Error, APICache::InvalidResponse, or any
+ # exception raised in the block passed to APICache.get
+ #
+ def get
+ check_queryable!
+ APICache.logger.debug "Fetching data from the API"
+ set_queried_at
+ Timeout::timeout(@timeout) do
+ if @block
+ # If this call raises an error then the response is not cached
+ @block.call
+ else
+ get_key_via_http
+ end
+ end
+ rescue Timeout::Error => e
+ raise APICache::TimeoutError, "Timed out when calling API (timeout #{@timeout}s)"
+ end
+
+ private
+
+ def get_key_via_http
+ response = redirecting_get(@key)
+ case response
+ when Net::HTTPSuccess
+ # 2xx response code
+ response.body
else
- APICache.logger.log "Queryable: false - queried too recently"
- false
+ raise APICache::InvalidResponse, "InvalidResponse http response: #{response.code}"
end
- else
- APICache.logger.log "Queryable: true - never used API before"
- true
end
- end
-
- # Fetch data from the API.
- #
- # If no block is given then the key is assumed to be a URL and which will
- # be queried expecting a 200 response. Otherwise the return value of the
- # block will be used.
- #
- # If the block is unable to fetch the value from the API it should raise
- # APICache::Invalid.
- def get(key, timeout, &block)
- APICache.logger.log "Fetching data from the API"
- @query_times[key] = Time.now
- Timeout::timeout(timeout) do
- if block_given?
- # This should raise APICache::Invalid if it is not correct
- yield
+
+ def redirecting_get(url)
+ r = Net::HTTP.get_response(URI.parse(url))
+ r.header['location'] ? redirecting_get(r.header['location']) : r
+ end
+
+ # Checks whether the API can be queried (i.e. whether :period has passed
+ # since the last query to the API).
+ #
+ def check_queryable!
+ if previously_queried?
+ if Time.now - queried_at > @period
+ APICache.logger.debug "Queryable: true - retry_time has passed"
+ else
+ APICache.logger.debug "Queryable: false - queried too recently"
+ raise APICache::CannotFetch,
+ "Cannot fetch #{@key}: queried too recently"
+ end
else
- get_via_http(key, timeout)
+ APICache.logger.debug "Queryable: true - never used API before"
end
end
- rescue Timeout::Error, APICache::Invalid => e
- raise APICache::CannotFetch, e.message
- end
-
-private
-
- def get_via_http(key, timeout)
- response = redirecting_get(key)
- case response
- when Net::HTTPSuccess
- # 2xx response code
- response.body
- else
- raise APICache::Invalid, "Invalid http response: #{response.code}"
+
+ def previously_queried?
+ APICache.store.exists?("#{@key}_queried_at")
end
- end
- def redirecting_get(url)
- r = Net::HTTP.get_response(URI.parse(url))
- r.header['location'] ? redirecting_get(r.header['location']) : r
+ def queried_at
+ APICache.store.get("#{@key}_queried_at")
+ end
+
+ def set_queried_at
+ APICache.store.set("#{@key}_queried_at", Time.now)
+ end
end
end