-
Notifications
You must be signed in to change notification settings - Fork 474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements relative cursor pagination. #594
Conversation
950c67f
to
9d6464b
Compare
def fetch_page(page_info) | ||
return [] unless page_info | ||
|
||
resource_class.where(original_params.merge(page_info: next_page_info)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do where(page_info: next_page_info)
but we're supporting old versions of ActiveResource, and where was added only in later versions.
9d6464b
to
7ac6eca
Compare
|
||
def fetch_next_page | ||
fetch_page(next_page_info) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about it, but decided to not memoize the next/previous page. If you're calling fetch, just have it fetch.
20af685
to
6c45cd0
Compare
6c45cd0
to
6600fae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach is good! A few small questions. Thanks for the great tests.
|
||
return nil unless link_header.present? | ||
|
||
CGI.parse(link_header.url.query)["page_info"][0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good place to dig
here, as this will raises if page_info
key is missing or not an array. Do we support ruby below 2.3 on this gem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s.required_ruby_version = ">= 2.4"
|
||
def pagination_link_headers | ||
@pagination_link_headers ||= ShopifyAPI::PaginationLinkHeaders.new( | ||
ShopifyAPI::Base.connection.response["Link"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I understand why we get a response off the base connection here.
Does that mean this gem is not threadsafe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ActiveResource appears to be threadsafe, and sets the connection class to be threadsafe.
We have a few places where we reference the base connection for response attributes. Here and here.
This was added in AR 4.1, and we only support >= 4.1
Of note, is that headers is explicitly handled as threadsafe in this gem, but that was done before the threadsafe stuff was added. I believe this can be removed, which I'll look into after this.
6600fae
to
f2c05be
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All makes sense to me. Thanks for doing this.
Should we update the readme? |
def fetch_page(page_info) | ||
return [] unless page_info | ||
|
||
resource_class.where(original_params.merge(page_info: page_info)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If your first request had the param order: 'title asc'
then this would merge page_info to that? Because that would make a bad URL. If we tried to request a page against Shopify with the original params + page_info it will fail if there are filters or sort orders in there.
We're probably better off just parsing out all the params out of the next/previous url and using them directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline, I'm going to take that approach, parse the next/previous page and use those for params.
ef49dad
to
688d3da
Compare
test/pagination_test.rb
Outdated
@@ -9,14 +9,12 @@ def setup | |||
@next_page_info = "eyJkaXJlY3Rpb24iOiJuZXh0IiwibGFzdF9pZCI6NDQwMDg5NDIzLCJsYXN0X3ZhbHVlIjoiNDQwMDg5NDIzIn0%3D" | |||
@previous_page_info = "eyJsYXN0X2lkIjoxMDg4MjgzMDksImxhc3RfdmFsdWUiOiIxMDg4MjgzMDkiLCJkaXJlY3Rpb24iOiJuZXh0In0%3D" | |||
|
|||
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@next_page_info}>; rel=\"next\"" | |||
@previous_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@previous_page_info}>; rel=\"previous\"" | |||
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@next_page_info}>; rel=\"next\">" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is right. The chevrons enclose the URL, not the whole tuple
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@next_page_info}>; rel=\"next\">" | |
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@next_page_info}>; rel=\"next\"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DrewMartin Fixed up for both.
test/pagination_test.rb
Outdated
@previous_page_info = "eyJsYXN0X2lkIjoxMDg4MjgzMDksImxhc3RfdmFsdWUiOiIxMDg4MjgzMDkiLCJkaXJlY3Rpb24iOiJuZXh0In0%3D" | ||
|
||
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@next_page_info}>; rel=\"next\">" | ||
@previous_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@previous_page_info}>; rel=\"previous\">" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@previous_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@previous_page_info}>; rel=\"previous\">" | |
@previous_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?page_info=#{@previous_page_info}>; rel=\"previous\"" |
test/pagination_test.rb
Outdated
|
||
test "uses all passed in querystring parameters" do | ||
params = "page_info=#{@next_page_info}&limit=50&fields=#{CGI.escape('id,created_at')}" | ||
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?#{params}>; rel=\"next\">" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?#{params}>; rel=\"next\">" | |
@next_link_header = "<https://this-is-my-test-shop.myshopify.com/admin/api/unstable/orders.json?#{params}>; rel=\"next\"" |
65ab254
to
42770b8
Compare
I've updated this to, instead of using Tested this here, with products with Tested with order (which is not yet converted). Finally: Tested with events with |
22c5ce0
to
8f5ae0a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM, just two questions
url = parts[0][/<(.*)>/, 1] | ||
rel = parts[1][/rel="(.*)"/, 1]&.to_sym | ||
|
||
url = URI.parse(url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add validations for url
+ rel
?
class Collection | ||
prepend ShopifyAPI::CollectionPagination | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed IRL do we have to solve the problem of querying the next/prev link after doing another query?, e.g.
page1 = Produdcts.find(:all)
page2 = Produdcts.find(:all)
page2.fetch_next_page
page1.next_page
page1.previous_page
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to parse the headers on collection initialization, vs lazily evaluating them. Also added a test for that case. The collection objects don't have access to the actual response, so we have to rely on ShopifyAPI::Base.connection.response["Link"]
, which is always for the last made request.
7e411e9
to
dc1d360
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, do you have plans for disabling page based pagination in the future like raising an error if they pass in a page param? I just worry that there are old tutorials out there that will be used and the debelopers will be confused why they are not getting more items (because the page param is ignored)
@tanema Thanks. Regarding disabling the page param, the API currently raises a fairly descriptive error with a link to the docs if a page param is passed in for a version of the API that isn't supported. Having a client side error raise would be helpful and would probably catch errors earlier for developers, rather than seeing it in production. I can look into adding this after this is merged. |
dc1d360
to
f16e479
Compare
❤️ It's happening! This is going to have a huge impact on adoption! |
🙏 |
Implements a convenient API for using our new relative cursor pagination, which has been partially released as of API version 2019-07.
Rather than providing an automatic way to iterate through all records in the query, we're adding the following methods to any model collection:
next_page?
previous_page?
fetch_next_page
fetch_previous_page
This allows you to perform the navigation however you see fit. Here is an example of using the API to iterate and process a list of all pending orders:
Currently making all endpoints available on the unstable API, and we can merge this when all endpoints have relative cursor pagination available in Shopify.
Tested: