shopify_api 7.0.2 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/CHANGELOG.md +7 -0
- data/README.md +30 -0
- data/Rakefile +1 -1
- data/lib/shopify_api.rb +2 -1
- data/lib/shopify_api/api_version.rb +17 -0
- data/lib/shopify_api/defined_versions.rb +1 -0
- data/lib/shopify_api/paginated_collection.rb +57 -0
- data/lib/shopify_api/pagination_link_headers.rb +33 -0
- data/lib/shopify_api/resources.rb +2 -1
- data/lib/shopify_api/resources/array_base.rb +13 -0
- data/lib/shopify_api/resources/base.rb +24 -21
- data/lib/shopify_api/resources/collect.rb +1 -0
- data/lib/shopify_api/resources/collection_listing.rb +10 -1
- data/lib/shopify_api/resources/customer_saved_search.rb +2 -0
- data/lib/shopify_api/resources/event.rb +1 -0
- data/lib/shopify_api/resources/metafield.rb +1 -0
- data/lib/shopify_api/resources/product.rb +2 -0
- data/lib/shopify_api/resources/product_listing.rb +8 -1
- data/lib/shopify_api/version.rb +1 -1
- data/shopify_api.gemspec +1 -1
- data/test/api_version_test.rb +14 -0
- data/test/base_test.rb +12 -6
- data/test/blog_test.rb +1 -1
- data/test/collection_listing_test.rb +38 -0
- data/test/collection_publication_test.rb +1 -1
- data/test/customer_test.rb +2 -2
- data/test/discount_code_test.rb +2 -2
- data/test/draft_order_test.rb +4 -4
- data/test/fixtures/collection_listing_product_ids2.json +1 -0
- data/test/fixtures/product_listing_product_ids.json +1 -1
- data/test/fixtures/product_listing_product_ids2.json +1 -0
- data/test/lib/webmock_extensions/last_request.rb +16 -0
- data/test/metafield_test.rb +1 -1
- data/test/pagination_test.rb +229 -0
- data/test/price_rule_test.rb +1 -1
- data/test/product_listing_test.rb +59 -2
- data/test/product_publication_test.rb +1 -1
- data/test/product_test.rb +1 -1
- data/test/recurring_application_charge_test.rb +3 -4
- data/test/shop_test.rb +1 -1
- data/test/tax_service_test.rb +2 -1
- data/test/test_helper.rb +83 -83
- metadata +12 -5
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== Version 7.1.0
|
2
|
+
|
3
|
+
* Add 2019-10 to known API versions
|
4
|
+
* Add support for cursor pagination [#594](https://github.com/Shopify/shopify_api/pull/594) and
|
5
|
+
[#611](https://github.com/Shopify/shopify_api/pull/611)
|
6
|
+
* `ShopifyAPI::Base.api_version` now defaults to `ShopifyAPI::ApiVersion::NullVersion` instead of `nil`. Making requests without first setting an ApiVersion raises `ApiVersionNotSetError` instead of `NoMethodError: undefined method 'construct_api_path' for nil:NilClass'` [#605](https://github.com/Shopify/shopify_api/pull/605)
|
7
|
+
|
1
8
|
== Version 7.0.2
|
2
9
|
|
3
10
|
* Add 2019-07 to known API versions.
|
data/README.md
CHANGED
@@ -365,6 +365,36 @@ ActiveResource is threadsafe as of version 4.1 (which works with Rails 4.x and a
|
|
365
365
|
|
366
366
|
If you were previously using Shopify's [activeresource fork](https://github.com/shopify/activeresource) then you should remove it and use ActiveResource 4.1.
|
367
367
|
|
368
|
+
## Pagination
|
369
|
+
|
370
|
+
Pagination can occur in one of two ways.
|
371
|
+
|
372
|
+
Page based pagination
|
373
|
+
```ruby
|
374
|
+
page = 1
|
375
|
+
products = ShopifyAPI::Product.find(:all, params: { limit: 50, page: page })
|
376
|
+
process_products(products)
|
377
|
+
while(products.count == 50)
|
378
|
+
page += 1
|
379
|
+
products = ShopifyAPI::Product.find(:all, params: { limit: 50, page: page })
|
380
|
+
process_products(products)
|
381
|
+
end
|
382
|
+
```
|
383
|
+
|
384
|
+
Page based pagination will be deprecated in the `2019-10` API version, in favor of the second method of pagination:
|
385
|
+
|
386
|
+
[Relative cursor based pagination](https://help.shopify.com/en/api/guides/paginated-rest-results)
|
387
|
+
```ruby
|
388
|
+
products = ShopifyAPI::Product.find(:all, params: { limit: 50 })
|
389
|
+
process_products(products)
|
390
|
+
while products.next_page?
|
391
|
+
products = products.fetch_next_page
|
392
|
+
process_products(products)
|
393
|
+
end
|
394
|
+
```
|
395
|
+
|
396
|
+
Relative cursor pagination is currently available for all endpoints using the `2019-10` and later API versions.
|
397
|
+
|
368
398
|
## Using Development Version
|
369
399
|
|
370
400
|
Download the source code and run:
|
data/Rakefile
CHANGED
data/lib/shopify_api.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
2
|
require 'active_resource'
|
4
3
|
require 'active_support/core_ext/class/attribute_accessors'
|
5
4
|
require 'digest/md5'
|
@@ -9,6 +8,7 @@ require 'shopify_api/limits'
|
|
9
8
|
require 'shopify_api/defined_versions'
|
10
9
|
require 'shopify_api/api_version'
|
11
10
|
require 'active_resource/json_errors'
|
11
|
+
require 'shopify_api/paginated_collection'
|
12
12
|
require 'shopify_api/disable_prefix_check'
|
13
13
|
|
14
14
|
module ShopifyAPI
|
@@ -21,6 +21,7 @@ require 'shopify_api/countable'
|
|
21
21
|
require 'shopify_api/resources'
|
22
22
|
require 'shopify_api/session'
|
23
23
|
require 'shopify_api/connection'
|
24
|
+
require 'shopify_api/pagination_link_headers'
|
24
25
|
|
25
26
|
if ShopifyAPI::Base.respond_to?(:connection_class)
|
26
27
|
ShopifyAPI::Base.connection_class = ShopifyAPI::Connection
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ShopifyAPI
|
3
3
|
class ApiVersion
|
4
|
+
class ApiVersionNotSetError < StandardError; end
|
4
5
|
class UnknownVersion < StandardError; end
|
5
6
|
class InvalidVersion < StandardError; end
|
6
7
|
|
@@ -109,5 +110,21 @@ module ShopifyAPI
|
|
109
110
|
construct_api_path('graphql.json')
|
110
111
|
end
|
111
112
|
end
|
113
|
+
|
114
|
+
class NullVersion
|
115
|
+
class << self
|
116
|
+
def stable?
|
117
|
+
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request."
|
118
|
+
end
|
119
|
+
|
120
|
+
def construct_api_path(*_path)
|
121
|
+
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request."
|
122
|
+
end
|
123
|
+
|
124
|
+
def construct_graphql_path
|
125
|
+
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request."
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
112
129
|
end
|
113
130
|
end
|
@@ -5,6 +5,7 @@ module ShopifyAPI
|
|
5
5
|
define_version(ShopifyAPI::ApiVersion::Unstable.new)
|
6
6
|
define_version(ShopifyAPI::ApiVersion::Release.new('2019-04'))
|
7
7
|
define_version(ShopifyAPI::ApiVersion::Release.new('2019-07'))
|
8
|
+
define_version(ShopifyAPI::ApiVersion::Release.new('2019-10'))
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyAPI
|
4
|
+
class PaginatedCollection < ActiveResource::Collection
|
5
|
+
module CollectionPagination
|
6
|
+
def initialize(args)
|
7
|
+
@next_url = pagination_link_headers.next_link&.url&.to_s
|
8
|
+
@previous_url = pagination_link_headers.previous_link&.url&.to_s
|
9
|
+
super(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def next_page?
|
13
|
+
ensure_available
|
14
|
+
@next_url.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def previous_page?
|
18
|
+
ensure_available
|
19
|
+
@previous_url.present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_next_page
|
23
|
+
fetch_page(@next_url)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch_previous_page
|
27
|
+
fetch_page(@previous_url)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
AVAILABLE_IN_VERSION = ShopifyAPI::ApiVersion::Release.new('2019-10')
|
33
|
+
AVAILABLE_IN_VERSION_EARLY = ShopifyAPI::ApiVersion::Release.new('2019-07')
|
34
|
+
|
35
|
+
def fetch_page(url)
|
36
|
+
ensure_available
|
37
|
+
return [] unless url.present?
|
38
|
+
|
39
|
+
resource_class.all(from: url)
|
40
|
+
end
|
41
|
+
|
42
|
+
def pagination_link_headers
|
43
|
+
@pagination_link_headers ||= ShopifyAPI::PaginationLinkHeaders.new(
|
44
|
+
ShopifyAPI::Base.connection.response["Link"]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def ensure_available
|
49
|
+
return if ShopifyAPI::Base.api_version >= AVAILABLE_IN_VERSION
|
50
|
+
return if ShopifyAPI::Base.api_version >= AVAILABLE_IN_VERSION_EARLY && resource_class.early_july_pagination?
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
include CollectionPagination
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ShopifyAPI
|
2
|
+
class InvalidPaginationLinksError < StandardError; end
|
3
|
+
|
4
|
+
class PaginationLinkHeaders
|
5
|
+
LinkHeader = Struct.new(:url, :rel)
|
6
|
+
attr_reader :previous_link, :next_link
|
7
|
+
|
8
|
+
def initialize(link_header)
|
9
|
+
links = parse_link_header(link_header)
|
10
|
+
@previous_link = links.find { |link| link.rel == :previous }
|
11
|
+
@next_link = links.find { |link| link.rel == :next }
|
12
|
+
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_link_header(link_header)
|
19
|
+
return [] unless link_header.present?
|
20
|
+
links = link_header.split(',')
|
21
|
+
links.map do |link|
|
22
|
+
parts = link.split('; ')
|
23
|
+
raise ShopifyAPI::InvalidPaginationLinksError.new("Invalid link header: url and rel expected") unless parts.length == 2
|
24
|
+
|
25
|
+
url = parts[0][/<(.*)>/, 1]
|
26
|
+
rel = parts[1][/rel="(.*)"/, 1]&.to_sym
|
27
|
+
|
28
|
+
url = URI.parse(url)
|
29
|
+
LinkHeader.new(url, rel)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -11,6 +11,8 @@ module ShopifyAPI
|
|
11
11
|
"ActiveResource/#{ActiveResource::VERSION::STRING}",
|
12
12
|
"Ruby/#{RUBY_VERSION}"].join(' ')
|
13
13
|
|
14
|
+
self.collection_parser = ShopifyAPI::PaginatedCollection
|
15
|
+
|
14
16
|
def encode(options = {})
|
15
17
|
same = dup
|
16
18
|
same.attributes = {self.class.element_name => same.attributes} if self.class.format.extension == 'json'
|
@@ -30,26 +32,13 @@ module ShopifyAPI
|
|
30
32
|
|
31
33
|
class << self
|
32
34
|
threadsafe_attribute(:_api_version)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
_headers ||= {}
|
42
|
-
end
|
43
|
-
end
|
44
|
-
else
|
45
|
-
def headers
|
46
|
-
if defined?(@headers)
|
47
|
-
@headers
|
48
|
-
elsif superclass != Object && superclass.headers
|
49
|
-
superclass.headers
|
50
|
-
else
|
51
|
-
@headers ||= {}
|
52
|
-
end
|
35
|
+
def headers
|
36
|
+
if _headers_defined?
|
37
|
+
_headers
|
38
|
+
elsif superclass != Object && superclass.headers
|
39
|
+
superclass.headers
|
40
|
+
else
|
41
|
+
_headers ||= {}
|
53
42
|
end
|
54
43
|
end
|
55
44
|
|
@@ -73,11 +62,13 @@ module ShopifyAPI
|
|
73
62
|
_api_version
|
74
63
|
elsif superclass != Object && superclass.site
|
75
64
|
superclass.api_version.dup.freeze
|
65
|
+
else
|
66
|
+
ApiVersion::NullVersion
|
76
67
|
end
|
77
68
|
end
|
78
69
|
|
79
70
|
def api_version=(version)
|
80
|
-
self._api_version = version.nil? ?
|
71
|
+
self._api_version = version.nil? ? ApiVersion::NullVersion : ApiVersion.coerce_to_version(version)
|
81
72
|
end
|
82
73
|
|
83
74
|
def prefix(options = {})
|
@@ -136,6 +127,18 @@ module ShopifyAPI
|
|
136
127
|
@prefix_options[resource_id]
|
137
128
|
end
|
138
129
|
end
|
130
|
+
|
131
|
+
def early_july_pagination?
|
132
|
+
!!early_july_pagination
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
attr_accessor :early_july_pagination
|
138
|
+
|
139
|
+
def early_july_pagination_release!
|
140
|
+
self.early_july_pagination = true
|
141
|
+
end
|
139
142
|
end
|
140
143
|
|
141
144
|
def persisted?
|
@@ -2,8 +2,17 @@ module ShopifyAPI
|
|
2
2
|
class CollectionListing < Base
|
3
3
|
self.primary_key = :collection_id
|
4
4
|
|
5
|
+
early_july_pagination_release!
|
6
|
+
|
5
7
|
def product_ids
|
6
|
-
|
8
|
+
ProductId.all(params: { collection_id: collection_id })
|
9
|
+
end
|
10
|
+
|
11
|
+
class ProductId < ArrayBase
|
12
|
+
self.resource_prefix = 'collection_listings/:collection_id/'
|
13
|
+
|
14
|
+
early_july_pagination_release!
|
7
15
|
end
|
16
|
+
private_constant :ProductId
|
8
17
|
end
|
9
18
|
end
|
@@ -2,8 +2,15 @@ module ShopifyAPI
|
|
2
2
|
class ProductListing < Base
|
3
3
|
self.primary_key = :product_id
|
4
4
|
|
5
|
+
early_july_pagination_release!
|
6
|
+
|
5
7
|
def self.product_ids
|
6
|
-
|
8
|
+
ProductId.all
|
9
|
+
end
|
10
|
+
|
11
|
+
class ProductId < ArrayBase
|
12
|
+
self.resource_prefix = 'product_listings/'
|
7
13
|
end
|
14
|
+
private_constant :ProductId
|
8
15
|
end
|
9
16
|
end
|
data/lib/shopify_api/version.rb
CHANGED
data/shopify_api.gemspec
CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.add_runtime_dependency("graphql-client")
|
31
31
|
|
32
32
|
s.add_development_dependency("mocha", ">= 0.9.8")
|
33
|
-
s.add_development_dependency("
|
33
|
+
s.add_development_dependency("webmock")
|
34
34
|
s.add_development_dependency("minitest", ">= 4.0")
|
35
35
|
s.add_development_dependency("rake")
|
36
36
|
s.add_development_dependency("timecop")
|
data/test/api_version_test.rb
CHANGED
@@ -136,6 +136,20 @@ class ApiVersionTest < Test::Unit::TestCase
|
|
136
136
|
)
|
137
137
|
end
|
138
138
|
|
139
|
+
test "NullVersion raises ApiVersionNotSetError" do
|
140
|
+
assert_raises(ShopifyAPI::ApiVersion::ApiVersionNotSetError) do
|
141
|
+
ShopifyAPI::ApiVersion::NullVersion.construct_api_path(:string)
|
142
|
+
end
|
143
|
+
|
144
|
+
assert_raises(ShopifyAPI::ApiVersion::ApiVersionNotSetError) do
|
145
|
+
ShopifyAPI::ApiVersion::NullVersion.construct_graphql_path
|
146
|
+
end
|
147
|
+
|
148
|
+
assert_raises(ShopifyAPI::ApiVersion::ApiVersionNotSetError) do
|
149
|
+
ShopifyAPI::ApiVersion::NullVersion.stable?
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
139
153
|
class TestApiVersion < ShopifyAPI::ApiVersion
|
140
154
|
def initialize(name)
|
141
155
|
@version_name = name
|
data/test/base_test.rb
CHANGED
@@ -33,9 +33,9 @@ class BaseTest < Test::Unit::TestCase
|
|
33
33
|
|
34
34
|
ShopifyAPI::Base.clear_session
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
assert_nil ShopifyAPI::Base.user
|
37
|
+
assert_nil ShopifyAPI::Base.password
|
38
|
+
assert_nil ShopifyAPI::Base.site
|
39
39
|
end
|
40
40
|
|
41
41
|
test '#clear_session should clear site and headers from Base' do
|
@@ -92,8 +92,9 @@ class BaseTest < Test::Unit::TestCase
|
|
92
92
|
test "prefix= will forward to resource when the value does not start with admin" do
|
93
93
|
session = ShopifyAPI::Session.new(domain: 'shop1.myshopify.com', token: 'token1', api_version: '2019-01')
|
94
94
|
ShopifyAPI::Base.activate_session(session)
|
95
|
-
|
96
|
-
|
95
|
+
silence_warnings do
|
96
|
+
TestResource.prefix = 'a/regular/path/'
|
97
|
+
end
|
97
98
|
|
98
99
|
assert_equal('/admin/api/2019-01/a/regular/path/', TestResource.prefix)
|
99
100
|
end
|
@@ -157,11 +158,16 @@ class BaseTest < Test::Unit::TestCase
|
|
157
158
|
assert_equal 2, ShopifyAPI::Shop.current.id
|
158
159
|
end
|
159
160
|
|
160
|
-
test "#api_version should set ApiVersion" do
|
161
|
+
test "#api_version= should set ApiVersion" do
|
161
162
|
ShopifyAPI::Base.api_version = '2019-04'
|
162
163
|
assert_equal '2019-04', ShopifyAPI::Base.api_version.to_s
|
163
164
|
end
|
164
165
|
|
166
|
+
test "#api_version= nil should set ApiVersion to ShopifyAPI::ApiVersion::NullVersion" do
|
167
|
+
ShopifyAPI::Base.api_version = nil
|
168
|
+
assert_equal ShopifyAPI::ApiVersion::NullVersion, ShopifyAPI::Base.api_version
|
169
|
+
end
|
170
|
+
|
165
171
|
def clear_header(header)
|
166
172
|
[ActiveResource::Base, ShopifyAPI::Base, ShopifyAPI::Product].each do |klass|
|
167
173
|
klass.headers.delete(header)
|