-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add seam.create_paginator
#272
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a39143d
Implement paginator class, middleware and seam method
andrii-balitskyi f77248f
Add test
andrii-balitskyi 7352136
ci: Format code
seambot ae11179
Simplify test
andrii-balitskyi a359dfd
Improve var naming
andrii-balitskyi 6a07d04
Update lib/seam/paginator.rb
andrii-balitskyi 95e40b1
Update spec/paginator_spec.rb
andrii-balitskyi b7af427
Add readme intructions on pagination
andrii-balitskyi f98e6bd
ci: Generate code
seambot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "faraday" | ||
| require_relative "http" | ||
|
|
||
| module Seam | ||
| THREAD_CONTEXT_KEY = :seam_pagination_context | ||
| PaginationContext = Struct.new(:pagination) | ||
|
|
||
| class Paginator | ||
| def initialize(request, params = {}) | ||
| raise ArgumentError, "request must be a Method" unless request.is_a?(Method) | ||
| raise ArgumentError, "params must be a Hash" unless params.is_a?(Hash) | ||
|
|
||
| @request = request | ||
| @params = params.transform_keys(&:to_sym) | ||
| end | ||
|
|
||
| def first_page | ||
| fetch_page(@params) | ||
| end | ||
|
|
||
| def next_page(next_page_cursor) | ||
| if next_page_cursor.nil? || next_page_cursor.empty? | ||
| raise ArgumentError, | ||
| "Cannot get the next page with a nil or empty next_page_cursor." | ||
| end | ||
|
|
||
| fetch_page(@params.merge(page_cursor: next_page_cursor)) | ||
| end | ||
|
|
||
| def flatten_to_list | ||
| all_items = [] | ||
| current_items, pagination = first_page | ||
|
|
||
| all_items.concat(current_items) if current_items | ||
|
|
||
| while pagination&.has_next_page? && (cursor = pagination.next_page_cursor) | ||
| current_items, pagination = next_page(cursor) | ||
| all_items.concat(current_items) if current_items | ||
| end | ||
|
|
||
| all_items | ||
| end | ||
|
|
||
| def flatten | ||
| Enumerator.new do |yielder| | ||
| current_items, pagination = first_page | ||
| current_items&.each { |item| yielder << item } | ||
|
|
||
| while pagination&.has_next_page? && (cursor = pagination.next_page_cursor) | ||
| current_items, pagination = next_page(cursor) | ||
| current_items&.each { |item| yielder << item } | ||
| end | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def fetch_page(params) | ||
| context = PaginationContext.new(nil) | ||
| Thread.current[THREAD_CONTEXT_KEY] = context | ||
|
|
||
| begin | ||
| res_data = @request.call(**params) | ||
| pagination_result = Pagination.from_hash(context.pagination) | ||
| [res_data, pagination_result] | ||
| ensure | ||
| Thread.current[THREAD_CONTEXT_KEY] = nil | ||
| end | ||
| end | ||
| end | ||
|
|
||
| Pagination = Struct.new(:has_next_page, :next_page_cursor, :next_page_url, keyword_init: true) do | ||
| def self.from_hash(hash) | ||
| return nil unless hash.is_a?(Hash) && !hash.empty? | ||
|
|
||
| new( | ||
| has_next_page: hash.fetch("has_next_page", false), | ||
| next_page_cursor: hash.fetch("next_page_cursor", nil), | ||
| next_page_url: hash.fetch("next_page_url", nil) | ||
| ) | ||
| end | ||
|
|
||
| def has_next_page? | ||
| has_next_page == true | ||
| end | ||
| end | ||
|
|
||
| class PaginationMiddleware < Faraday::Middleware | ||
| def on_complete(env) | ||
| context = Thread.current[THREAD_CONTEXT_KEY] | ||
| return unless context.is_a?(PaginationContext) | ||
|
|
||
| pagination_hash = extract_pagination(env) | ||
| context.pagination = pagination_hash if pagination_hash | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def extract_pagination(env) | ||
| body = env[:body] | ||
| body["pagination"] if body.is_a?(Hash) && body.key?("pagination") | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "spec_helper" | ||
| require "seam/paginator" | ||
|
|
||
| RSpec.describe Seam::Paginator do | ||
| around do |example| | ||
| with_fake_seam_connect do |seam, _endpoint, _seed| | ||
| @seam = seam | ||
| example.run | ||
| end | ||
| end | ||
|
|
||
| let(:seam) { @seam } | ||
|
|
||
| describe "#first_page" do | ||
| it "fetches the first page of results and pagination info" do | ||
| paginator = seam.create_paginator(seam.connected_accounts.method(:list), {limit: 2}) | ||
| connected_accounts, pagination = paginator.first_page | ||
|
|
||
| expect(connected_accounts).to be_a(Array) | ||
| expect(connected_accounts.size).to eq(2) | ||
| expect(connected_accounts.first).to be_a(Seam::Resources::ConnectedAccount) | ||
|
|
||
| expect(pagination).to be_a(Seam::Pagination) | ||
| expect(pagination.has_next_page?).to be true | ||
| expect(pagination.next_page_cursor).to be_a(String) | ||
| expect(pagination.next_page_url).to match(%r{/connected_accounts/list.*[?&]next_page_cursor=}) | ||
| end | ||
| end | ||
|
|
||
| describe "#next_page" do | ||
| it "fetches the next page of results" do | ||
| paginator = seam.create_paginator(seam.connected_accounts.method(:list), {limit: 2}) | ||
| _first_page_accounts, first_pagination = paginator.first_page | ||
|
|
||
| expect(first_pagination.has_next_page?).to be true | ||
|
|
||
| next_page_accounts, next_pagination = paginator.next_page(first_pagination.next_page_cursor) | ||
|
|
||
| expect(next_page_accounts).to be_a(Array) | ||
| expect(next_page_accounts.size).to eq(1) # 3 total accounts, limit 2 -> page 1 has 2, page 2 has 1 | ||
| expect(next_page_accounts.first).to be_a(Seam::Resources::ConnectedAccount) | ||
|
|
||
| expect(next_pagination).to be_a(Seam::Pagination) | ||
| expect(next_pagination.has_next_page?).to be false | ||
| expect(next_pagination.next_page_cursor).to be_nil | ||
| end | ||
|
|
||
| it "raises ArgumentError if next_page_cursor is nil or empty" do | ||
| paginator = seam.create_paginator(seam.connected_accounts.method(:list), {limit: 2}) | ||
| expect { paginator.next_page(nil) }.to raise_error(ArgumentError, /nil or empty next_page_cursor/) | ||
| expect { paginator.next_page("") }.to raise_error(ArgumentError, /nil or empty next_page_cursor/) | ||
| end | ||
| end | ||
|
|
||
| describe "#flatten_to_list" do | ||
| it "fetches all items from all pages into a single list" do | ||
| total_accounts = seam.connected_accounts.list.size | ||
| paginator = seam.create_paginator(seam.connected_accounts.method(:list), {limit: 1}) | ||
| paginated_accounts = paginator.flatten_to_list | ||
|
|
||
| expect(paginated_accounts).to be_a(Array) | ||
| expect(paginated_accounts.size).to be > 1 | ||
| expect(paginated_accounts.size).to eq(total_accounts) | ||
| expect(paginated_accounts.first).to be_a(Seam::Resources::ConnectedAccount) | ||
| end | ||
| end | ||
|
|
||
| describe "#flatten" do | ||
| it "returns an Enumerator that yields all items from all pages" do | ||
| total_accounts = seam.connected_accounts.list.size | ||
| paginator = seam.create_paginator(seam.connected_accounts.method(:list), {limit: 1}) | ||
|
|
||
| collected_accounts = [] | ||
| paginator.flatten.each do |account| | ||
| collected_accounts << account | ||
| end | ||
|
|
||
| expect(collected_accounts).to be_a(Array) | ||
| expect(collected_accounts.size).to be > 1 | ||
| expect(collected_accounts.size).to eq(total_accounts) | ||
| expect(collected_accounts.first).to be_a(Seam::Resources::ConnectedAccount) | ||
| end | ||
| end | ||
| end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.