-
Notifications
You must be signed in to change notification settings - Fork 552
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8d379bf
commit ba38dc9
Showing
5 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 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,19 @@ | ||
# frozen_string_literal: true | ||
|
||
module Stripe | ||
module APIOperations | ||
module Search | ||
def _search(search_url, filters = {}, opts = {}) | ||
opts = Util.normalize_opts(opts) | ||
|
||
resp, opts = execute_resource_request(:get, search_url, filters, opts) | ||
obj = SearchResultObject.construct_from(resp.data, opts) | ||
|
||
# set filters so that we can fetch the same limit and query | ||
# when accessing the next page | ||
obj.filters = filters.dup | ||
obj | ||
end | ||
end | ||
end | ||
end |
This file contains 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 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 | ||
|
||
module Stripe | ||
class SearchResultObject < StripeObject | ||
include Enumerable | ||
include Stripe::APIOperations::Search | ||
include Stripe::APIOperations::Request | ||
|
||
OBJECT_NAME = "search_result" | ||
|
||
# This accessor allows a `SearchResultObject` to inherit various filters | ||
# that were given to a predecessor. This allows for things like consistent | ||
# limits, expansions, and predicates as a user pages through resources. | ||
attr_accessor :filters | ||
|
||
# An empty search result object. This is returned from +next+ when we know | ||
# that there isn't a next page in order to replicate the behavior of the API | ||
# when it attempts to return a page beyond the last. | ||
def self.empty_search_result(opts = {}) | ||
SearchResultObject.construct_from({ data: [] }, opts) | ||
end | ||
|
||
def initialize(*args) | ||
super | ||
self.filters = {} | ||
end | ||
|
||
def [](key) | ||
case key | ||
when String, Symbol | ||
super | ||
else | ||
raise ArgumentError, | ||
"You tried to access the #{key.inspect} index, but " \ | ||
"SearchResultObject types only support String keys. " \ | ||
"(HINT: Search calls return an object with a 'data' (which is " \ | ||
"the data array). You likely want to call #data[#{key.inspect}])" | ||
end | ||
end | ||
|
||
# Iterates through each resource in the page represented by the current | ||
# `SearchListObject`. | ||
# | ||
# Note that this method makes no effort to fetch a new page when it gets to | ||
# the end of the current page's resources. See also +auto_paging_each+. | ||
def each(&blk) | ||
data.each(&blk) | ||
end | ||
|
||
# Returns true if the page object contains no elements. | ||
def empty? | ||
data.empty? | ||
end | ||
|
||
# Iterates through each resource in all pages, making additional fetches to | ||
# the API as necessary. | ||
# | ||
# Note that this method will make as many API calls as necessary to fetch | ||
# all resources. For more granular control, please see +each+ and | ||
# +next_search_result_page+. | ||
def auto_paging_each(&blk) | ||
return enum_for(:auto_paging_each) unless block_given? | ||
|
||
page = self | ||
|
||
loop do | ||
page.each(&blk) | ||
page = page.next_search_result_page | ||
|
||
break if page.empty? | ||
end | ||
end | ||
|
||
# Fetches the next page in the resource list (if there is one). | ||
# | ||
# This method will try to respect the limit of the current page. If none | ||
# was given, the default limit will be fetched again. | ||
def next_search_result_page(params = {}, opts = {}) | ||
return self.class.empty_search_result(opts) unless has_more | ||
|
||
params = filters.merge(page: next_page).merge(params) | ||
|
||
_search(url, params, opts) | ||
end | ||
end | ||
end |
This file contains 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,132 @@ | ||
# frozen_string_literal: true | ||
|
||
require ::File.expand_path("../test_helper", __dir__) | ||
|
||
module Stripe | ||
class SearchResultObjectTest < Test::Unit::TestCase | ||
should "provide .empty_list" do | ||
list = Stripe::SearchResultObject.empty_search_result | ||
assert list.empty? | ||
end | ||
|
||
should "provide #count via enumerable" do | ||
list = Stripe::SearchResultObject.construct_from(data: [{ object: "charge" }]) | ||
assert_equal 1, list.count | ||
end | ||
|
||
should "provide #each" do | ||
arr = [ | ||
{ id: 1 }, | ||
{ id: 2 }, | ||
{ id: 3 }, | ||
] | ||
expected = Util.convert_to_stripe_object(arr, {}) | ||
list = Stripe::SearchResultObject.construct_from(data: arr) | ||
assert_equal expected, list.each.to_a | ||
end | ||
|
||
should "provide #auto_paging_each that supports forward pagination" do | ||
arr = [ | ||
{ id: 1 }, | ||
{ id: 2 }, | ||
{ id: 3 }, | ||
{ id: 4 }, | ||
{ id: 5 }, | ||
{ id: 6 }, | ||
] | ||
|
||
list = TestSearchResultObject.construct_from({ data: [{ id: 1 }], | ||
has_more: true, | ||
next_page: "next_page_token_1", | ||
url: "/things", }) | ||
list.filters = { limit: 3 } | ||
|
||
# The test will start with the synthetic search result object above, and uses the | ||
# 'next_page' token to fetch two more pages. The second page indicates | ||
# that there are no more elements by setting `has_more` to `false`, and | ||
# iteration stops. | ||
stub_request(:get, "#{Stripe.api_base}/things") | ||
.with(query: { limit: 3, page: "next_page_token_1" }) | ||
.to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }, { id: 4 }], has_more: true, url: "/things", next_page: "next_page_token_2")) | ||
stub_request(:get, "#{Stripe.api_base}/things") | ||
.with(query: { limit: 3, page: "next_page_token_2" }) | ||
.to_return(body: JSON.generate(data: [{ id: 5 }, { id: 6 }], has_more: false, url: "/things", next_page: nil)) | ||
|
||
assert_equal arr, list.auto_paging_each.to_a.map(&:to_hash) | ||
end | ||
|
||
should "provide #auto_paging_each that responds to a block" do | ||
arr = [ | ||
{ id: 1 }, | ||
{ id: 2 }, | ||
{ id: 3 }, | ||
] | ||
expected = Util.convert_to_stripe_object(arr, {}) | ||
|
||
list = TestSearchResultObject.construct_from(data: [{ id: 1 }], | ||
has_more: true, | ||
next_page: "next_page_token_1", | ||
url: "/things") | ||
|
||
stub_request(:get, "#{Stripe.api_base}/things") | ||
.with(query: { page: "next_page_token_1" }) | ||
.to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }], has_more: false)) | ||
|
||
actual = [] | ||
list.auto_paging_each do |obj| | ||
actual << obj | ||
end | ||
|
||
assert_equal expected, actual | ||
end | ||
|
||
should "provide #empty?" do | ||
list = Stripe::SearchResultObject.construct_from(data: []) | ||
assert list.empty? | ||
list = Stripe::SearchResultObject.construct_from(data: [{}]) | ||
refute list.empty? | ||
end | ||
|
||
# | ||
# next_page | ||
# | ||
|
||
should "fetch a next page through #next_page" do | ||
list = TestSearchResultObject.construct_from(data: [{ id: 1 }], | ||
has_more: true, | ||
next_page: "next_page_token_1", | ||
url: "/things") | ||
stub_request(:get, "#{Stripe.api_base}/things") | ||
.with(query: { page: "next_page_token_1" }) | ||
.to_return(body: JSON.generate(data: [{ id: 2 }], has_more: false)) | ||
next_list = list.next_search_result_page | ||
refute next_list.empty? | ||
assert_equal [{ id: 2 }], next_list.auto_paging_each.to_a.map(&:to_hash) | ||
end | ||
|
||
should "fetch a next page through #next_page and respect limit" do | ||
list = TestSearchResultObject.construct_from(data: [{ id: 1 }], | ||
has_more: true, | ||
next_page: "next_page_token_1", | ||
url: "/things") | ||
list.filters = { limit: 3 } | ||
stub_request(:get, "#{Stripe.api_base}/things") | ||
.with(query: { "limit": 3, page: "next_page_token_1" }) | ||
.to_return(body: JSON.generate(data: [{ id: 2 }], has_more: false)) | ||
next_list = list.next_search_result_page | ||
assert_equal({ limit: 3, page: "next_page_token_1" }, next_list.filters) | ||
end | ||
|
||
should "fetch an empty page through #next_page" do | ||
list = TestSearchResultObject.construct_from(data: [{ id: 1 }], | ||
has_more: false, | ||
url: "/things") | ||
next_list = list.next_search_result_page | ||
assert_equal Stripe::SearchResultObject.empty_search_result, next_list | ||
end | ||
end | ||
end | ||
|
||
# A helper class with a URL that allows us to try out pagination. | ||
class TestSearchResultObject < Stripe::SearchResultObject | ||
end |