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