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: 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.
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
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']
gem "shopify_api" gem "shopify-api-limits"
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
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 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