christocracy / shopify-api-limits

A simple Gem for reading shopify API call limits
17 stars 15 forks source link

= shopify-api-limits

My friend Dave (aka "hunkybill") posted a problem to me one day about ShopifyAPI call limits, offering a case of beer if I could find a solution: http://forums.shopify.com/categories/9/posts/49003

So in the HTTP headers, the ShopifyAPI will return to you in each API request how many calls you've made, as well as the maximum number of calls available.

== Problem ActiveResource does not make it easy to read the HTTP response headers, since the method #request in ActiveResource::Connection does not save a reference to the HTTP response:

Makes a request to the remote service.

def request(method, path, arguments) result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload| payload[:method] = method payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}" payload[:result] = http.send(method, path, arguments) end handle_response(result) # <-- right here: handle_response returns an instance of HTTPResponse but doesn't save a ref to it! rescue Timeout::Error => e raise TimeoutError.new(e.message) rescue OpenSSL::SSL::SSLError => e raise SSLError.new(e.message) end

== Solution Hack ActiveResource::Connection to introduce a new attr_reader :response and capture the returned instance of HTTPResponse provided by net/http from the #handle_response method.

module ActiveResource class Connection

HACK: Add an attr_reader for response

  attr_reader :response

  # capture the original #handle_response as an unbound method instead of using alias 
  handle_response = self.instance_method(:handle_response)

  # re-implement #handle_response to capture the returned HTTPResponse to an instance var.    
  define_method(:handle_response) do |response| 
    @response = handle_response.bind(self).call(response)
  end 
end

end

Now it's possible to access the HTTPResponse instance directly from ActiveResource, via: foo = ActiveResource::Base.connection.response['http-header-param-foo']

== Installation gem "shopify_api" gem "shopify-api-limits"

== Usage count_shop = ShopifyAPI.credit_used :shop limit_shop = ShopifyAPI.credit_limit :shop

count_global = ShopifyAPI.credit_used :global limit_global = ShopifyAPI.credit_limit :global

Generally, you shouldn't need to use the methods above directly -- rather, they're used under-the-hood by the following helpful methods which don't require a scope (:shop/:global): If the :global scope has fewer calls available than the :local scope, the methods will operate upon the :global scope; otherwise, values will be returned based upon the :shop scope.

unless ShopifyAPI.credit_maxed?

make a ShopifyAPI call

end

until ShopifyAPI.credit_maxed? || stop_condition

make some ShopifyAPI calls

end

while ShopifyAPI.credit_left || stop_condition

make some ShopifyAPI calls

end

== A special bonus for retrieving large recordsets > 250 records Shopify places a hard limit of 250 on the number of records returned in a single request. There are ways around this, including one listed here http://bit.ly/kgwCRc which involves manually calculating the total & number of pages then iterating to make several API calls. This gem encapsulates this behaviour allowing you to make what feels like a single call to the ShopifyAPI. Simply set :params => {:limit => false}. False as in, "no limit"

For example, imagine a store which has 251 orders and you want to fetch them all. Since the maximum number of records returned in a single request is 250, 2 requests must be made to the API in order to fetch all 251 records.

records = ShopifyAPI::Order.all(:params => {:limit => false}) puts records.count => 251

Without {:limit => false}, the normal behaviour will operate (ie: just one request with 250 records returned):

records = ShopifyAPI::Order.all(:params => {:limit => 250}) puts records.count => 250

If you don't have enough API credits to perform the multiple requests to serve your desired recordset, a ShopifyAPI::Limits::Error will be raised: puts ShopifyAPI::Order.count => 251

puts ShopifyAPI.credit_left => 1

begin rs = ShopifyAPI::Order.all(:params => {:limit => false}) rescue ShopifyAPI::Limits::Error puts "Uhoh...didn't have enough credits to do that. Maybe you wanna' queue your task for a later date." end