From dd06c2faa3b379410ff7c99d6376010b7fb84c1c Mon Sep 17 00:00:00 2001
From: Domizio Demichelis
Date: Sat, 14 Dec 2024 17:31:51 +0700
Subject: [PATCH] Shorten the cutoff string by using an array in place of a
hash
---
docs/api/keyset.md | 30 ++++----
docs/api/keyset_for_ui.md | 8 +--
docs/extras/keyset_for_ui.md | 18 ++---
gem/lib/pagy/keyset.rb | 10 ++-
test/pagy/extras/headers_test.rb | 5 +-
test/pagy/extras/headers_test.rb.yaml | 100 +++++++++++++-------------
test/pagy/extras/jsonapi_test.rb | 2 +-
test/pagy/extras/jsonapi_test.rb.yaml | 28 ++++----
test/pagy/extras/keyset_test.rb | 16 ++---
test/pagy/keyset_for_ui_test.rb | 26 +++----
test/pagy/keyset_test.rb | 70 ++++++++----------
11 files changed, 148 insertions(+), 165 deletions(-)
diff --git a/docs/api/keyset.md b/docs/api/keyset.md
index 381cac9ed..acdad8a11 100644
--- a/docs/api/keyset.md
+++ b/docs/api/keyset.md
@@ -52,7 +52,7 @@ If you want the best of the two worlds, check out the [keyset_for_ui extra](/doc
| `set` | The `uniquely ordered` `ActiveRecord::Relation` or `Sequel::Dataset` collection to paginate. |
| `keyset` | The hash of column/direction pairs. Pagy extracts it from the order of the `set`. |
| `keyset attributes` | The hash of keyset-column/record-value pairs of a record. |
-| `cutoff` | A point in the `set` that separates the records of two contiguous `page`s. It's the encoded string of the `keyset attributes` of the last record of a `page`. |
+| `cutoff` | A point in the `set` where a `page` ended. Its value is a `Base64` encoded URL-safe string. |
| `page` | The current `page`, i.e. the page of records beginning after the `cutoff` of the previous page. Also the `:page` variable, which is set to the `cutoff` of the previous page |
| `next` | The next `page`, i.e. the page of records beginning after the `cutoff`. Also the `cutoff` value retured by the `next` method. |
@@ -164,29 +164,24 @@ If you need a specific order:
#### Understanding the Cutoffs
-A `cutoff` defines a point in the `set`, right AFTER the last record of a `page`.
+A `cutoff` defines a point in the `set` where a `page` ended. All the records AFTER that point are or will be part of the `next` page.
Let's consider an example of a simple `set`. In order to avoid confusion with numeric ids and number of records, let's assume that
-it that has an `id` column that is actually a unique alphanumeric code, and its order is:
-`order(:id)`.
+it has an `id` column populated by unique alphanumeric codes, and its order is: `order(:id)`.
-Assuming a LIMIT of 6, the first page will include the first 6 records in the set: no `cutoff` required so
-far...
+Assuming a LIMIT of 6, the first page will include the first 6 records in the set: no `cutoff` required so far...
```
| page | not yet paginated |
beginning ->|. . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . . . .|<- end of set
```
-After we pull the 6 records, we read the `id` of the last one in the page, which is `F`. So our `cutoff` can be defined like: _"
-the point after the value `F` in the `id` column"_.
+After we pull the first 6 records from the beginning of the `set`, we read the `id` of the last one, which is `F`. So our `cutoff` can be defined like: _"the point up to the value `F` in the `id` column"_.
-Notice that this is not like saying _"after the record `F`"_. It's important to understand that a `cutoff` refers just to a value
-in a column (or multiple column in case of muti-columns keysets) after which there will be the next page.
-
-Indeed, that very record could be deleted right after we read it, and our `cutoff` will still be the valid start for the next page
-of 6 records after the `cutoff-F`...
+Notice that this is not like saying _"up to the record `F`"_. It's important to understand that a `cutoff` refers just to a value
+in a column (or a combination of multiple column, in case of muti-columns keysets).
+Indeed, that very record could be deleted right after we read it, and our `cutoff` will still be the valid reference that _"we paginated the `set`, up to the "F" value"_...
```
| page | page | not yet paginated |
beginning ->|. . . . . F]. . . . . .|. . . . . . . . . . . . . . . . . . . . .|<- end of set
@@ -194,8 +189,7 @@ beginning ->|. . . . . F]. . . . . .|. . . . . . . . . . . . . . . . . . . . .|<
cutoff-F
```
-Again, after we pull the next 6 records, we read the `id` of the last one in the page, which is `L`: so we have our new
-`cutoff-L`, which is the start of the next `page`...
+For getting the `next` page of records - this time - we pull the `next` 6 records AFTER the `cutoff-F`. Again, we read the `id` of the last one, which is `L`: so we have our new `cutoff-L`, which is the end of the current `page`, and the `next` will go AFTER it...
```
| page | page | page | not yet paginated |
@@ -204,7 +198,7 @@ beginning ->|. . . . . F]. . . . . L]. . . . . .|. . . . . . . . . . . . . . .|<
cutoff-F cutoff-L
```
-Pagy encodes the values of the `cutoffs` in a `Base64` URL-safe string that is used in the request as a param.
+Pagy encodes the values of the `cutoffs` in a `Base64` URL-safe string that is sent as a param in the `request`.
## ORMs
@@ -254,7 +248,7 @@ Default `nil`.
==- `:jsonify_keyset_attributes`
-A lambda to override the generic json encoding of the `keyset` attributes. Use it when the generic `to_json` method would lose
+A lambda to override the generic json encoding of the `keyset` attributes. It receives the keyset attributes to jsonify, and it should return a JSON string of the `attributes.values` array. Use it when the generic `to_json` method would lose
some information when decoded.
For example: `Time` objects may lose or round the fractional seconds through the encoding/decoding cycle, causing the ordering to
@@ -266,7 +260,7 @@ etc.). Here is what you can do:
jsonify_keyset_attributes = lambda do |attributes|
# Convert it to a string matching the stored value/format in SQLite DB
attributes[:created_at] = attributes[:created_at].strftime('%F %T.%6N')
- attributes.to_json
+ attributes.values.to_json # remember to return an array of the values only
end
Pagy::Keyset(set, jsonify_keyset_attributes:)
diff --git a/docs/api/keyset_for_ui.md b/docs/api/keyset_for_ui.md
index 860a929d3..1dc99661f 100644
--- a/docs/api/keyset_for_ui.md
+++ b/docs/api/keyset_for_ui.md
@@ -33,11 +33,11 @@ You should also familiarize with the [Pagy::Keyset](keyset.md) class.
This section integrates the [Keyset Glossary](keyset_for_ui.md#glossary)
-| Term | Description |
-|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Term | Description |
+|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `keyset pagination for UI` | The pagy exclusive technique to use `keyset pagination` with numeric pages, supporting `pagy_*navs` and the other Frontend helpers.
The best technique for performance AND functionality! |
-| `page` | The current page **number** |
-| `cutoffs` | The `cutoff`s of the pagination known so far, used to keep track of the visited pages. |
+| `page` | The current page **number** |
+| `cutoffs` | The `cutoff`s of the known pagination state, used to keep track of the visited pages during the navigation. |
## How Pagy Keyset For UI works
diff --git a/docs/extras/keyset_for_ui.md b/docs/extras/keyset_for_ui.md
index 6e6dd9d9d..6d024e63f 100644
--- a/docs/extras/keyset_for_ui.md
+++ b/docs/extras/keyset_for_ui.md
@@ -17,11 +17,10 @@ and the other Frontend helpers.
## Overview
-This extra manages the cache used by the `Pagy::KeysetForUI` instance, allowing easy customization and integration with your
-app.
+This extra manages the cache used by the `Pagy::KeysetForUI` instance, allowing easy customization and integration with your app.
-It also adds a `pagy_keyset_for_ui` constructor method that can be used in your controllers, and provides the automatic setting
-of the variables from the request `params`.
+It also adds a `pagy_keyset_for_ui` constructor method that can be used in your controllers, and provides the automatic setting of
+the variables from the request `params`.
Please refer to the following resource:
@@ -53,8 +52,8 @@ def pagy_cache_new_key = my_custom_cache.generate_key
## Understanding the cache
-This extra uses the `session` object as the cache for the `cutoffs` (not the records!) by default, because it's simple and works in any app, at least for
-prototyping.
+This extra uses the `session` object as the cache for the `cutoffs` (not for the records!) by default, because it's simple and
+works in any app, at least for prototyping.
Notice that the `cutoffs` array can potentially grow big if you don't use `:max_pages`, especially if your `keyset` contains
multiple ordered columns and more if their size is big. You must be aware of it.
@@ -69,8 +68,8 @@ session as the cache (e.g. `ActiveRecord::SessionStore`).
!!!warning
-Besides writing and reading from it, Pagy does not expire nor handle the cache in any way. Your app should manage it (e.g. like it does
-with the `session` object).
+Besides writing and reading from it, Pagy does not expire nor handle the cache in any way. Your app should manage it (e.g. like it
+does with the `session` object).
!!!
This extra uses only 3 simple methods to handle the cache:
@@ -86,7 +85,8 @@ handling other aspects of it (e.g. expiration, etc.)
We are considering implementing a client-side cache using the Browser's `sessionStorage`.
-It might considerably simplify the handling of the cache, but it will require some time to design it properly, so please, hang tight and cheer for us!
+It might considerably simplify the handling of the cache, but it will require some time to design it properly, so please, hang
+tight and cheer for us!
!!!
## Variables
diff --git a/gem/lib/pagy/keyset.rb b/gem/lib/pagy/keyset.rb
index 8bc3dfe69..94dcfb623 100644
--- a/gem/lib/pagy/keyset.rb
+++ b/gem/lib/pagy/keyset.rb
@@ -105,10 +105,8 @@ def after_cutoff_sql(prefix = nil)
# Decode a cutoff, check its consistency and returns the cutoff args
def cutoff_to_args(cutoff)
- args = JSON.parse(B64.urlsafe_decode(cutoff)).transform_keys(&:to_sym)
- raise InternalError, 'cutoff and keyset are not consistent' \
- unless args.keys == @keyset.keys
-
+ values = JSON.parse(B64.urlsafe_decode(cutoff))
+ args = @keyset.keys.zip(values).to_h
typecast_args(args)
end
@@ -123,8 +121,8 @@ def default
# Derive the cutoff from the last record
def derive_cutoff
- hash = keyset_attributes_from(@records.last)
- json = @vars[:jsonify_keyset_attributes]&.(hash) || hash.to_json
+ attr = keyset_attributes_from(@records.last)
+ json = @vars[:jsonify_keyset_attributes]&.(attr) || attr.values.to_json
B64.urlsafe_encode(json)
end
diff --git a/test/pagy/extras/headers_test.rb b/test/pagy/extras/headers_test.rb
index 5ddf4d825..147642f34 100644
--- a/test/pagy/extras/headers_test.rb
+++ b/test/pagy/extras/headers_test.rb
@@ -79,10 +79,13 @@
it 'returns custom headers hash' do
pagy, _records = app.send(:pagy_keyset,
Pet.order(:id),
- page: 'eyJpZCI6MjB9',
+ page: 'WzIwXQ',
headers: { limit: 'Per-Page', page: 'Page', count: 'Total', pages: false })
_(app.send(:pagy_headers, pagy)).must_rematch :headers
end
+ # -{"link"=>"; rel=\"first\", ; rel=\"next\"", "Page"=>"eyJpZCI6MjB9", "Per-Page"=>"20"}
+ # +{"link"=>"; rel=\"first\"", "Page"=>"eyJpZCI6MjB9", "Per-Page"=>"20"}
+
it 'omit next on last page' do
pagy, _records = app.send(:pagy_keyset, Pet.order(:id), limit: 50)
_(app.send(:pagy_headers, pagy)).must_rematch :headers
diff --git a/test/pagy/extras/headers_test.rb.yaml b/test/pagy/extras/headers_test.rb.yaml
index 6bcbf00b5..cd583904e 100644
--- a/test/pagy/extras/headers_test.rb.yaml
+++ b/test/pagy/extras/headers_test.rb.yaml
@@ -1,47 +1,10 @@
---
-pagy/extras/headers___pagy_headers_merge_test_0001_returns_the_full_headers_hash:
- :response: !ruby/hash:Rack::Headers
- link: ; rel="first", ;
- rel="prev", ; rel="next", ;
- rel="last"
- current-page: '3'
- page-items: '20'
- total-pages: '50'
- total-count: '1000'
-pagy/extras/headers___pagy_headers_merge_with_Calendar_test_0001_returns_the_full_headers_hash:
- :response: !ruby/hash:Rack::Headers
- link: ; rel="first", ;
- rel="prev", ; rel="next", ;
- rel="last"
- current-page: '3'
- page-items: '20'
- total-pages: '26'
- total-count: '505'
-pagy/extras/headers___pagy_headers_with_Keyset_test_0003_omit_next_on_last_page:
- :headers:
- link: ; rel="first"
- page-items: '50'
-pagy/extras/headers___pagy_headers_with_Keyset_test_0002_returns_custom_headers_hash:
- :headers:
- link: ; rel="first", ;
- rel="next"
- Page: eyJpZCI6MjB9
- Per-Page: '20'
-pagy/extras/headers___pagy_headers_with_Keyset_test_0001_returns_the_full_headers_hash:
- :headers:
- link: ; rel="first", ;
- rel="next"
- page-items: '20'
-pagy/extras/headers___pagy_headers_test_0004_returns_the_countless_headers_hash:
- :headers:
- link: ; rel="first", ;
- rel="next"
- current-page: '1'
- page-items: '20'
-pagy/extras/headers___pagy_headers_test_0003_returns_custom_headers_hash:
+pagy/extras/headers___pagy_headers_test_0002_returns_custom_headers_hash:
:headers:
link: ; rel="first", ;
rel="next", ; rel="last"
+ Per-Page: '20'
+ Total: '1000'
pagy/extras/headers___pagy_headers_test_0006_omit_next_on_last_page:
:headers:
link: ; rel="first", ;
@@ -50,6 +13,12 @@ pagy/extras/headers___pagy_headers_test_0006_omit_next_on_last_page:
page-items: '20'
total-pages: '50'
total-count: '1000'
+pagy/extras/headers___pagy_headers_test_0004_returns_the_countless_headers_hash:
+ :headers:
+ link: ; rel="first", ;
+ rel="next"
+ current-page: '1'
+ page-items: '20'
pagy/extras/headers___pagy_headers_test_0001_returns_the_full_headers_hash:
:headers:
link: ; rel="first", ;
@@ -58,12 +27,10 @@ pagy/extras/headers___pagy_headers_test_0001_returns_the_full_headers_hash:
page-items: '20'
total-pages: '50'
total-count: '1000'
-pagy/extras/headers___pagy_headers_test_0002_returns_custom_headers_hash:
+pagy/extras/headers___pagy_headers_test_0003_returns_custom_headers_hash:
:headers:
link: ; rel="first", ;
rel="next", ; rel="last"
- Per-Page: '20'
- Total: '1000'
pagy/extras/headers___pagy_headers_test_0005_omit_prev_on_first_page:
:headers:
link: ; rel="first", ;
@@ -72,16 +39,45 @@ pagy/extras/headers___pagy_headers_test_0005_omit_prev_on_first_page:
page-items: '20'
total-pages: '50'
total-count: '1000'
-pagy/extras/headers___pagy_headers_with_Calendar_test_0004_returns_the_countless_headers_hash:
+pagy/extras/headers___pagy_headers_merge_test_0001_returns_the_full_headers_hash:
+ :response: !ruby/hash:Rack::Headers
+ link: ; rel="first", ;
+ rel="prev", ; rel="next", ;
+ rel="last"
+ current-page: '3'
+ page-items: '20'
+ total-pages: '50'
+ total-count: '1000'
+pagy/extras/headers___pagy_headers_with_Keyset_test_0002_returns_custom_headers_hash:
:headers:
- link: ; rel="first", ;
+ link: ; rel="first", ;
+ rel="next"
+ Page: WzIwXQ
+ Per-Page: '20'
+pagy/extras/headers___pagy_headers_with_Keyset_test_0003_omit_next_on_last_page:
+ :headers:
+ link: ; rel="first"
+ page-items: '50'
+pagy/extras/headers___pagy_headers_with_Keyset_test_0001_returns_the_full_headers_hash:
+ :headers:
+ link: ; rel="first", ;
rel="next"
- current-page: '1'
page-items: '20'
-pagy/extras/headers___pagy_headers_with_Calendar_test_0003_returns_custom_headers_hash:
+pagy/extras/headers___pagy_headers_merge_with_Calendar_test_0001_returns_the_full_headers_hash:
+ :response: !ruby/hash:Rack::Headers
+ link: ; rel="first", ;
+ rel="prev", ; rel="next", ;
+ rel="last"
+ current-page: '3'
+ page-items: '20'
+ total-pages: '26'
+ total-count: '505'
+pagy/extras/headers___pagy_headers_with_Calendar_test_0002_returns_custom_headers_hash:
:headers:
link: ; rel="first", ;
rel="next", ; rel="last"
+ Per-Page: '20'
+ Total: '505'
pagy/extras/headers___pagy_headers_with_Calendar_test_0006_omit_next_on_last_page:
:headers:
link: ; rel="first", ;
@@ -90,6 +86,12 @@ pagy/extras/headers___pagy_headers_with_Calendar_test_0006_omit_next_on_last_pag
page-items: '20'
total-pages: '26'
total-count: '505'
+pagy/extras/headers___pagy_headers_with_Calendar_test_0004_returns_the_countless_headers_hash:
+ :headers:
+ link: ; rel="first", ;
+ rel="next"
+ current-page: '1'
+ page-items: '20'
pagy/extras/headers___pagy_headers_with_Calendar_test_0001_returns_the_full_headers_hash:
:headers:
link: ; rel="first", ;
@@ -98,12 +100,10 @@ pagy/extras/headers___pagy_headers_with_Calendar_test_0001_returns_the_full_head
page-items: '20'
total-pages: '26'
total-count: '505'
-pagy/extras/headers___pagy_headers_with_Calendar_test_0002_returns_custom_headers_hash:
+pagy/extras/headers___pagy_headers_with_Calendar_test_0003_returns_custom_headers_hash:
:headers:
link: ; rel="first", ;
rel="next", ; rel="last"
- Per-Page: '20'
- Total: '505'
pagy/extras/headers___pagy_headers_with_Calendar_test_0005_omit_prev_on_first_page:
:headers:
link: ; rel="first", ;
diff --git a/test/pagy/extras/jsonapi_test.rb b/test/pagy/extras/jsonapi_test.rb
index 0109b0a2d..1a7e20783 100644
--- a/test/pagy/extras/jsonapi_test.rb
+++ b/test/pagy/extras/jsonapi_test.rb
@@ -95,7 +95,7 @@
end
describe '#pagy_jsonapi_links (keyset)' do
it 'returns the ordered links' do
- app = MockApp.new(params: { page: { latest: 'eyJpZCI6MTB9', size: 10 } })
+ app = MockApp.new(params: { page: { latest: 'WzIwXQ', size: 10 } })
pagy, _records = app.send(:pagy_keyset,
Pet.order(:id),
page_param: :latest,
diff --git a/test/pagy/extras/jsonapi_test.rb.yaml b/test/pagy/extras/jsonapi_test.rb.yaml
index 5ada83907..032aa25fa 100644
--- a/test/pagy/extras/jsonapi_test.rb.yaml
+++ b/test/pagy/extras/jsonapi_test.rb.yaml
@@ -1,27 +1,27 @@
---
-pagy/extras/jsonapi___pagy_jsonapi_links_(keyset)_test_0002_sets_the_next_value_to_null_when_the_link_is_unavailable:
+pagy/extras/jsonapi___pagy_jsonapi_links_(keyset)_test_0001_returns_the_ordered_links:
:keyset_result:
- :first: "/foo?page%5Bsize%5D=50&page%5Blatest%5D"
+ :first: "/foo?page%5Blatest%5D&page%5Bsize%5D=10"
:last:
:prev:
- :next:
-pagy/extras/jsonapi___pagy_jsonapi_links_(keyset)_test_0001_returns_the_ordered_links:
+ :next: "/foo?page%5Blatest%5D=WzMwXQ&page%5Bsize%5D=10"
+pagy/extras/jsonapi___pagy_jsonapi_links_(keyset)_test_0002_sets_the_next_value_to_null_when_the_link_is_unavailable:
:keyset_result:
- :first: "/foo?page%5Blatest%5D&page%5Bsize%5D=10"
+ :first: "/foo?page%5Bsize%5D=50&page%5Blatest%5D"
:last:
:prev:
- :next: "/foo?page%5Blatest%5D=eyJpZCI6MjB9&page%5Bsize%5D=10"
+ :next:
+pagy/extras/jsonapi__JsonApi_test_0001_uses_the__jsonapi_with_page_nil:
+ :url_1: "/foo?page%5Bpage%5D=1"
+ :url_2: "/foo?page%5Bpage%5D=1&page%5Blimit%5D=20"
+pagy/extras/jsonapi__JsonApi_test_0002_uses_the__jsonapi_with_page_3:
+ :url_1: "/foo?page%5Bpage%5D=2"
+ :url_2: "/foo?page%5Bpage%5D=2&page%5Blimit%5D=20"
+pagy/extras/jsonapi__JsonApi_with_custom_named_params_test_0002_sets_custom_named_params:
+ :url: "/foo?page%5Bnumber%5D=4&page%5Bsize%5D=10"
pagy/extras/jsonapi___pagy_jsonapi_links_test_0001_returns_the_ordered_links:
:result:
:first: "/foo?page%5Bnumber%5D=1&page%5Bsize%5D=10"
:last: "/foo?page%5Bnumber%5D=100&page%5Bsize%5D=10"
:prev: "/foo?page%5Bnumber%5D=2&page%5Bsize%5D=10"
:next: "/foo?page%5Bnumber%5D=4&page%5Bsize%5D=10"
-pagy/extras/jsonapi__JsonApi_with_custom_named_params_test_0002_sets_custom_named_params:
- :url: "/foo?page%5Bnumber%5D=4&page%5Bsize%5D=10"
-pagy/extras/jsonapi__JsonApi_test_0002_uses_the__jsonapi_with_page_3:
- :url_1: "/foo?page%5Bpage%5D=2"
- :url_2: "/foo?page%5Bpage%5D=2&page%5Blimit%5D=20"
-pagy/extras/jsonapi__JsonApi_test_0001_uses_the__jsonapi_with_page_nil:
- :url_1: "/foo?page%5Bpage%5D=1"
- :url_2: "/foo?page%5Bpage%5D=1&page%5Blimit%5D=20"
diff --git a/test/pagy/extras/keyset_test.rb b/test/pagy/extras/keyset_test.rb
index 28afb416d..c928b103f 100644
--- a/test/pagy/extras/keyset_test.rb
+++ b/test/pagy/extras/keyset_test.rb
@@ -18,32 +18,32 @@
limit: 10)
_(pagy).must_be_kind_of Pagy::Keyset
_(records.size).must_equal 10
- _(pagy.next).must_equal "eyJhbmltYWwiOiJjYXQiLCJuYW1lIjoiRWxsYSIsImlkIjoxOH0"
+ _(pagy.next).must_equal "WyJjYXQiLCJFbGxhIiwxOF0"
end
it 'pulls the page from params' do
- app = MockApp.new(params: { page: "eyJpZCI6MTB9", limit: 10 })
+ app = MockApp.new(params: { page: "WzEwXQ", limit: 10 })
pagy, records = app.send(:pagy_keyset,
model.order(:id),
tuple_comparison: true)
_(records.first.id).must_equal 11
- _(pagy.next).must_equal "eyJpZCI6MjB9"
+ _(pagy.next).must_equal "WzIwXQ"
end
end
describe 'URL helpers' do
it 'returns the URLs for first page' do
- app = MockApp.new(params: { page: "eyJpZCI6MTB9", limit: 10 })
+ app = MockApp.new(params: { page: nil, limit: 10 })
pagy, _records = app.send(:pagy_keyset, model.order(:id))
_(app.send(:pagy_keyset_first_url, pagy)).must_equal "/foo?page&limit=10"
- _(app.send(:pagy_keyset_next_url, pagy)).must_equal "/foo?page=eyJpZCI6MjB9&limit=10"
+ _(app.send(:pagy_keyset_next_url, pagy)).must_equal "/foo?page=WzEwXQ&limit=10"
end
it 'returns the URLs for second page' do
- app = MockApp.new(params: { page: "eyJpZCI6MjB9", limit: 10 })
+ app = MockApp.new(params: { page: "WzEwXQ", limit: 10 })
pagy, _records = app.send(:pagy_keyset, model.order(:id))
_(app.send(:pagy_keyset_first_url, pagy)).must_equal "/foo?page&limit=10"
- _(app.send(:pagy_keyset_next_url, pagy)).must_equal "/foo?page=eyJpZCI6MzB9&limit=10"
+ _(app.send(:pagy_keyset_next_url, pagy)).must_equal "/foo?page=WzIwXQ&limit=10"
end
it 'returns the URLs for last page' do
- app = MockApp.new(params: { page: "eyJpZCI6NDB9", limit: 10 })
+ app = MockApp.new(params: { page: "WzQwXQ", limit: 10 })
pagy, _records = app.send(:pagy_keyset, model.order(:id))
_(app.send(:pagy_keyset_first_url, pagy)).must_equal "/foo?page&limit=10"
_(app.send(:pagy_keyset_next_url, pagy)).must_be_nil
diff --git a/test/pagy/keyset_for_ui_test.rb b/test/pagy/keyset_for_ui_test.rb
index 88e9f4ea9..ef7380645 100644
--- a/test/pagy/keyset_for_ui_test.rb
+++ b/test/pagy/keyset_for_ui_test.rb
@@ -11,7 +11,7 @@
describe 'uses optional variables' do
it 'use the :tuple_comparison' do
pagy = Pagy::KeysetForUI.new(model.order(:animal, :name, :id),
- cutoffs: [nil, nil, "eyJhbmltYWwiOiJjYXQiLCJuYW1lIjoiRWxsYSIsImlkIjoxOH0"],
+ cutoffs: [nil, nil, "WyJjYXQiLCJFbGxhIiwxOF0"],
page: 2,
limit: 10,
tuple_comparison: true)
@@ -21,10 +21,10 @@
end
it 'uses :jsonify_keyset_attributes' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9"],
+ cutoffs: [nil, nil, "WzEwXQ"],
page: 2,
limit: 10,
- jsonify_keyset_attributes: lambda(&:to_json))
+ jsonify_keyset_attributes: ->(attr) { attr.values.to_json })
_(pagy.next).must_equal(3)
_(pagy.instance_variable_get(:@cutoff_args)).must_equal(id: 10)
end
@@ -39,19 +39,19 @@
end
it 'handles the page/cut for the second page' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9"],
+ cutoffs: [nil, nil, "WzEwXQ"],
limit: 10,
page: 2)
_(pagy.instance_variable_get(:@cutoff_args)).must_equal(id: 10)
_(pagy.records.first.id).must_equal 11
_(pagy.next).must_equal 3
- _(pagy.cutoffs).must_equal [nil, nil, "eyJpZCI6MTB9", "eyJpZCI6MjB9"]
+ _(pagy.cutoffs).must_equal [nil, nil, "WzEwXQ", "WzIwXQ"]
end
it 'handles the page/cut for the last page' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6NDB9"],
+ cutoffs: [nil, nil, "WzEwXQ", "WzIwXQ", "WzMwXQ", "WzQwXQ"],
limit: 10,
- page: 2)
+ page: 5)
_(pagy.instance_variable_get(:@cutoff_args)).must_equal(id: 40)
_(pagy.records.first.id).must_equal 41
_(pagy.next).must_be_nil
@@ -61,27 +61,27 @@
it 'raises OverflowError' do
_ do
Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9"],
+ cutoffs: [nil, nil, "WzEwXQ"],
limit: 10,
page: 3)
end.must_raise Pagy::OverflowError
end
it 'resets overflow' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9", "eyJpZCI6MjB9"],
+ cutoffs: [nil, nil, "WzEwXQ", "WzIwXQ"],
reset_overflow: true,
limit: 10,
page: 4)
_(pagy.instance_variable_get(:@cutoff_args)).must_be_nil
_(pagy.records.first.id).must_equal 1
_(pagy.next).must_equal 2
- _(pagy.cutoffs).must_equal [nil, nil, "eyJpZCI6MTB9"]
+ _(pagy.cutoffs).must_equal [nil, nil, "WzEwXQ"]
end
end
describe 'handles the jumping back' do
it 'handles the assign_cut_args jump back to the first page' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9"], # last visited 2
+ cutoffs: [nil, nil, "WzEwXQ"], # last visited 2
page: 1,
limit: 10)
_(pagy.instance_variable_get(:@cut)).must_be_nil
@@ -90,13 +90,13 @@
end
it 'handles the assign_cut_args jump back to the second page' do
pagy = Pagy::KeysetForUI.new(model.order(:id),
- cutoffs: [nil, nil, "eyJpZCI6MTB9", "eyJpZCI6MjB9"],
+ cutoffs: [nil, nil, "WzEwXQ", "WzIwXQ"],
page: 2,
limit: 10)
_(pagy.instance_variable_get(:@cutoff_args)).must_equal({ :id => 10, :cutoff_id => 20 })
_(pagy.records.first.id).must_equal 11
_(pagy.next).must_equal 3
- _(pagy.cutoffs).must_equal [nil, nil, "eyJpZCI6MTB9", "eyJpZCI6MjB9"]
+ _(pagy.cutoffs).must_equal [nil, nil, "WzEwXQ", "WzIwXQ"]
end
end
describe 'other requirements' do
diff --git a/test/pagy/keyset_test.rb b/test/pagy/keyset_test.rb
index e81a6c12e..4d8d21426 100644
--- a/test/pagy/keyset_test.rb
+++ b/test/pagy/keyset_test.rb
@@ -24,8 +24,8 @@
_(Pagy::Keyset.new(model.order(:id))).must_be_kind_of Pagy::Keyset
end
it 'raises Pagy::InternalError for inconsistent page/keyset' do
- page_animal_id = Pagy::B64.urlsafe_encode({animal: 'dog', id: 23}.to_json)
- err = assert_raises(Pagy::InternalError) do
+ page_animal_id = Pagy::B64.urlsafe_encode({ animal: 'dog', id: 23 }.to_json)
+ err = assert_raises(Pagy::InternalError) do
Pagy::Keyset.new(model.order(:id), limit: 10, page: page_animal_id)
end
assert_match(/cutoff and keyset are not consistent/, err.message)
@@ -33,52 +33,39 @@
end
describe 'uses optional variables' do
it 'use the :tuple_comparison' do
- pagy = Pagy::Keyset.new(model.order(:animal, :name, :id),
- page: "eyJhbmltYWwiOiJjYXQiLCJuYW1lIjoiRWxsYSIsImlkIjoxOH0",
- limit: 10,
- tuple_comparison: true)
+ pagy = Pagy::Keyset.new(model.order(:animal, :name, :id),
+ page: "WyJjYXQiLCJFbGxhIiwxOF0",
+ limit: 10,
+ tuple_comparison: true)
records = pagy.records
_(records.size).must_equal 10
_(records.first.id).must_equal 13
end
it 'uses :jsonify_keyset_attributes' do
pagy = Pagy::Keyset.new(model.order(:id),
- page: "eyJpZCI6MTB9",
- limit: 10,
- jsonify_keyset_attributes: lambda(&:to_json))
+ page: "WzEwXQ",
+ limit: 10,
+ jsonify_keyset_attributes: ->(attr) { attr.values.to_json })
_(pagy.next).must_equal("eyJpZCI6MjB9")
- _(pagy.instance_variable_get(:@cutoff_args)).must_equal({id: 10})
- end
- it 'uses :filter_records' do
- filter_records = if model == Pet
- ->(set, filter_args, _keyset) { set.where('id > :id', **filter_args) }
- else
- ->(set, filter_args, _keyset) { set.where(Sequel.lit('id > :id', **filter_args)) }
- end
- pagy = Pagy::Keyset.new(model.order(:id),
- page: "eyJpZCI6MTB9",
- limit: 10,
- filter_records:)
- records = pagy.records
- _(records.first.id).must_equal(11)
+ _(pagy.instance_variable_get(:@cutoff_args)).must_equal({ id: 10 })
end
end
describe '#extract_keyset' do
it 'extracts the keyset from the set order (single column)' do
pagy = Pagy::Keyset.new(model.order(:id))
- _(pagy.instance_variable_get(:@keyset)).must_equal({:id => :asc})
+ _(pagy.instance_variable_get(:@keyset)).must_equal({ :id => :asc })
set = model == Pet ? model.order(id: :desc) : model.order(Sequel.desc(:id))
pagy = Pagy::Keyset.new(set)
- _(pagy.instance_variable_get(:@keyset)).must_equal({:id => :desc})
+ _(pagy.instance_variable_get(:@keyset)).must_equal({ :id => :desc })
end
it 'extracts the keyset from the set order (multiple columns)' do
- set = if model == Pet
- model.order(animal: :desc, id: :asc)
- else
- model.order(Sequel.desc(:animal), Sequel.asc(:id))
- end
+ set = if model == Pet
+ model.order(animal: :desc, id: :asc)
+ else
+ model.order(Sequel.desc(:animal), Sequel.asc(:id))
+ end
pagy = Pagy::Keyset.new(set)
- _(pagy.instance_variable_get(:@keyset)).must_equal({animal: :desc, :id => :asc})
+ _(pagy.instance_variable_get(:@keyset)).must_equal({ animal: :desc, :id => :asc })
end
if model == PetSequel
it 'raises TypeError for unknown order type' do
@@ -87,12 +74,12 @@
it 'skips unrestricted primary keys' do
model.unrestrict_primary_key
Pagy::Keyset.new(model.order(:id),
- page: "eyJpZCI6MTB9",
+ page: "WzEwXQ",
limit: 10)
_(model.restrict_primary_key?).must_equal false
model.restrict_primary_key
Pagy::Keyset.new(model.order(:id),
- page: "eyJpZCI6MTB9",
+ page: "WzEwXQ",
limit: 10)
_(model.restrict_primary_key?).must_equal true
end
@@ -102,16 +89,16 @@
it 'handles the page/cut for the first page' do
pagy = Pagy::Keyset.new(model.order(:id), limit: 10)
_(pagy.instance_variable_get(:@cut)).must_be_nil
- _(pagy.next).must_equal "eyJpZCI6MTB9"
+ _(pagy.next).must_equal "WzEwXQ"
end
it 'handles the page/cut for the second page' do
- pagy = Pagy::Keyset.new(model.order(:id), limit: 10, page: "eyJpZCI6MTB9")
+ pagy = Pagy::Keyset.new(model.order(:id), limit: 10, page: "WzEwXQ")
_(pagy.instance_variable_get(:@cutoff_args)).must_equal(id: 10)
_(pagy.records.first.id).must_equal 11
- _(pagy.next).must_equal "eyJpZCI6MjB9"
+ _(pagy.next).must_equal "WzIwXQ"
end
it 'handles the page/cut for the last page' do
- pagy = Pagy::Keyset.new(model.order(:id), limit: 10, page: "eyJpZCI6NDB9")
+ pagy = Pagy::Keyset.new(model.order(:id), limit: 10, page: "WzQwXQ")
_(pagy.instance_variable_get(:@cutoff_args)).must_equal(id: 40)
_(pagy.records.first.id).must_equal 41
_(pagy.next).must_be_nil
@@ -119,8 +106,8 @@
end
describe 'other requirements' do
it 'adds the required columns to the selected values' do
- set = model.order(:animal, :name, :id).select(:name)
- pagy = Pagy::Keyset.new(set, limit: 10)
+ set = model.order(:animal, :name, :id).select(:name)
+ pagy = Pagy::Keyset.new(set, limit: 10)
pagy.records
set = pagy.instance_variable_get(:@set)
_((model == Pet ? set.select_values : set.opts[:select]).sort).must_equal %i[animal id name]
@@ -132,6 +119,7 @@ def slurp_by_page(page: nil, records: [], &block)
records << result[:records]
result[:page] ? slurp_by_page(page: result[:page], records:, &block) : records
end
+
mixed_set = if model == Pet
model.order(animal: :asc, birthdate: :desc, id: :asc)
elsif model == PetSequel
@@ -141,9 +129,9 @@ def slurp_by_page(page: nil, records: [], &block)
model.order(:animal, :name, :id),
mixed_set].each_with_index do |set, i|
it "pulls all the records in set#{i} without repetions" do
- pages = slurp_by_page do |page|
+ pages = slurp_by_page do |page|
pagy = Pagy::Keyset.new(set, page:, limit: 9)
- {records: pagy.records, page: pagy.next}
+ { records: pagy.records, page: pagy.next }
end
collection = set.to_a
_(collection.size).must_equal 50