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