Skip to content

Commit

Permalink
Merge 925423c into d83d5f9
Browse files Browse the repository at this point in the history
  • Loading branch information
awood45 committed Oct 27, 2017
2 parents d83d5f9 + 925423c commit 5d22403
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Aws::Record::ItemCollection - Add the `#page` and `#last_evaluated_key` methods to `Aws::Record::ItemCollection`. This helps to support use cases where you'd like to control the result set size with the `:limit` parameter, or if you want to expose pagination capabilities to an outside caller, for example a list-type operation exposed in a web API.

2.0.0 (2017-08-29)
------------------

Expand Down
60 changes: 60 additions & 0 deletions features/searching/search.feature
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,63 @@ Feature: Amazon DynamoDB Querying and Scanning
}
]
"""

@wip
Scenario: Paginate Manually With Multiple Calls
When we call the 'scan' class method with parameter data:
"""
{
"limit": 2
}
"""
Then we should receive an aws-record page with 2 values from members:
"""
[
{
"id": "1",
"count": 5,
"body": "First item."
},
{
"id": "1",
"count": 10,
"body": "Second item."
},
{
"id": "1",
"count": 15,
"body": "Third item."
},
{
"id": "2",
"count": 10,
"body": "Fourth item."
}
]
"""
When we call the 'scan' class method using the page's pagination token
Then we should receive an aws-record page with 2 values from members:
"""
[
{
"id": "1",
"count": 5,
"body": "First item."
},
{
"id": "1",
"count": 10,
"body": "Second item."
},
{
"id": "1",
"count": 15,
"body": "Third item."
},
{
"id": "2",
"count": 10,
"body": "Fourth item."
}
]
"""
24 changes: 24 additions & 0 deletions features/searching/step_definitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@
@collection = @model.scan
end

When(/^we call the 'scan' class method with parameter data:$/) do |string|
data = JSON.parse(string, symbolize_names: true)
@collection = @model.scan(data)
end

When(/^we take the first member of the result collection$/) do
@instance = @collection.first
end

Then(/^we should receive an aws\-record page with 2 values from members:$/) do |string|
expected = JSON.parse(string, symbolize_names: true)
page = @collection.page
@last_evaluated_key = @collection.last_evaluated_key
# This is definitely a hack which takes advantage of an accident in test
# design. In the future, we'll need to have some sort of shared collection
# state to cope with the fact that scan order is not guaranteed.
page.size == 2
# Results do not have guaranteed order, check each expected value individually
page.each do |item|
h = item.to_h
expect(expected.any? { |e| h == e }).to eq(true)
end
end

When(/^we call the 'scan' class method using the page's pagination token$/) do
@collection = @model.scan(exclusive_start_key: @last_evaluated_key)
end
27 changes: 27 additions & 0 deletions lib/aws-record/record/item_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,40 @@ def initialize(search_method, search_params, model, client)
def each(&block)
return enum_for(:each) unless block_given?
items.each_page do |page|
@last_evaluated_key = page.last_evaluated_key
items_array = _build_items_from_response(page.items, @model)
items_array.each do |item|
yield item
end
end
end

# Provides the first "page" of responses from your query operation. This
# will only make a single client call, and will provide the items, if any
# exist, from that response. It will not attempt to follow up on
# pagination tokens, so this is not guaranteed to include all items that
# match your search.
#
# @return [Array<Aws::Record>] an array of the record items found in the
# first page of reponses from the query or scan call.
def page
search_response = items
@last_evaluated_key = search_response.last_evaluated_key
_build_items_from_response(search_response.items, @model)
end

# Provides the pagination key most recently used by the underlying client.
# This can be useful in the case where you're exposing pagination to an
# outside caller, and want to be able to "resume" your scan in a new call
# without starting over.
#
# @return [Hash] a hash representing an attribute key/value pair, suitable
# for use as the +exclusive_start_key+ in another query or scan
# operation. If there are no more pages in the result, will be nil.
def last_evaluated_key
@last_evaluated_key
end

# Checks if the query/scan result is completely blank.
#
# WARNING: This can and will query your entire partition, or scan your
Expand Down
97 changes: 87 additions & 10 deletions spec/aws-record/record/item_collection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,94 @@ module Record
}
end

describe "#each" do
let(:non_truncated_resp) do
{
items: [
{ "id" => 4 },
{ "id" => 5 }
],
count: 2,
last_evaluated_key: nil
}
let(:non_truncated_resp) do
{
items: [
{ "id" => 4 },
{ "id" => 5 }
],
count: 2,
last_evaluated_key: nil
}
end

describe "#page" do
it "provides an array of items from a single client call" do
stub_client.stub_responses(:scan, truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
actual = c.page
expect(actual.size).to eq(3)
actual_ids = actual.map { |a| a.id }
expect(actual_ids).to eq([1,2,3])
expect(c.last_evaluated_key).to eq({"id" => { "n" => "3" }})
end
end

describe "#last_evaluated_key" do
it "points you to the client response pagination value if present" do
stub_client.stub_responses(:scan, truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
c.take(2) # Trigger the "call"
expect(c.last_evaluated_key).to eq({"id" => { "n" => "3" }})
end

it "provides a nil pagination value if no pages remain" do
stub_client.stub_responses(:scan, non_truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
c.take(2) # Trigger the "call"
expect(c.last_evaluated_key).to be_nil
end

it "correctly provides the most recent pagination key" do
stub_client.stub_responses(:scan, truncated_resp, non_truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
c.take(4) # Trigger the "call" and the second "page"
expect(c.last_evaluated_key).to be_nil
end

it "gathers evaluation keys from #page as well" do
stub_client.stub_responses(:scan, truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
c.page
expect(c.last_evaluated_key).to eq({"id" => { "n" => "3" }})
stub_client.stub_responses(:scan, non_truncated_resp)
c = ItemCollection.new(
:scan,
{ table_name: "TestTable" },
model,
stub_client
)
c.page
expect(c.last_evaluated_key).to be_nil
end
end

describe "#each" do

it "correctly iterates through a paginated response" do
stub_client.stub_responses(:scan, truncated_resp, non_truncated_resp)
Expand Down

0 comments on commit 5d22403

Please sign in to comment.