diff --git a/docs/api/countless.md b/docs/api/countless.md index 49a5afd7a..ee0f04ccc 100644 --- a/docs/api/countless.md +++ b/docs/api/countless.md @@ -47,9 +47,9 @@ The `pagy_info` and all the `*_combo_nav_js` helpers that use the total `count` Instead of basing all the internal calculations on the `:count` variable (passed with the constructor), this class uses the number of actually retrieved items to deduce the pagination variables. -The retrieved items number is passed in a second step with the `finalize` method, and it allows to determine if there is a `next` page, or if the current page is the `last` page, or if the current request should raise a `Pagy::OverflowError` exception. - -The trick is retrieving `items + 1`, and using the resulting number to calculate the variables, while eventually removing the extra item from the result. (see the [countless.rb extra](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/countless.rb)) +The retrieved items number can be passed in a second step to the `finalize` method, wich allows to determine if there is a `next` page, or if the current page is the `last` page, or if the current request should raise a `Pagy::OverflowError` exception. + +Retrieving these variables may be useful to supply a UI as complete as possible, when used with classic helpers, and can be skipped when it's not needed (like for navless pagination, infinite-scroll, etc.). See the [countless.rb extra](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/countless.rb) for more details. ## Methods diff --git a/docs/extras/countless.md b/docs/extras/countless.md index c7a08d205..3007a28f5 100644 --- a/docs/extras/countless.md +++ b/docs/extras/countless.md @@ -3,11 +3,7 @@ title: Countless --- # Countless Extra -This extra uses the `Pagy::Countless` subclass in order to avoid to execute an otherwise needed count query. It is especially useful when used with large DB tables, where [Caching the count](../how-to.md#caching-the-count) may not be an option. - -Its usage is practically the same as the regular `Pagy::Backend` module (see the [backend doc](../api/backend.md)). - -The pagination resulting from this extra has some limitation as documented in the [Pagy::Countless Caveats doc](../api/countless.md#caveats). +This extra uses the `Pagy::Countless` subclass in order to save one count query per request. It is especially useful when used with large DB tables, where [Caching the count](../how-to.md#caching-the-count) may not be an option, or when there is no need to have a classic UI. Please read also the [Pagy::Countless doc](../api/countless.md) for a full understanding of its features and limitations. ## Synopsis @@ -17,13 +13,51 @@ In the `pagy.rb` initializer: ```ruby require 'pagy/extras/countless' +# optionally enable the minimal mode by default +# Pagy::VARS[:countless_minimal] = true ``` In a controller: ```ruby -@pagy, @records = pagy_countless(some_scope, ...) +# default mode (eager loading) +@pagy, @records = pagy_countless(some_scope, ...) + +# OR +# enable minimal mode for this instance (lazy loading) +@pagy, @records = pagy_countless(some_scope, countless_minimal: true, ...) ``` + +## Modes + +This extra can be used in two different modes by enabling or not the `:countless_minimal` variable. + +### Default mode + +This is the preferred automatic way to save one query per request, while keep using the classic pagination UI helpers. + +By default this extra will try to finalize the `pagy` object with all the available variables in a countless pagination. It will do so by retrieving `items + 1`, and using the resulting number to calculate the variables, while eventually removing the extra item from the result. + +That means: + +- The `pagy` object will know whether the current page is the last one or there will be a next page so you can use it right away with any supported helper +- The returned paginated collection (`@records`) will be an `Array` instead of a scope (so the records are already eager-loaded from the DB) + +### Minimal mode + +This is the preferred mode used to implement navless and automatic incremental/infinite-scroll pagination, where there is no need to use any UI. + +If you enable the `:countless_minimal` variable, then: + +- The returned `pagy` object will contain just a handful of variables and will miss the finalization, so you cannnot use it with any helpers +- The returned paginated collection (`@records`) will be a regular scope (i.e. no record has been load yet) so an eventual fragment caching can work as expected +- You will need to check the size of the paginated collection (`@records`) in order to know if it is the last page or not. You can tell if it by checking `@records.size < @pagy.vars[:items]`. Notice that IF the last page has exactly the `@pagy.vars[:items]` in it you will not be able to know it. In infinite scroll that would just try to load the next page returning 0 items, so it will be perfectly acceptable anyway. + +## Variables + +| Variable | Description | Default | +|:---------------------|:----------------------------------|:--------| +| `:countless_minimal` | enable the countless minimal mode | `false` | ## Files @@ -35,7 +69,7 @@ All the methods in this module are prefixed with the `"pagy_countless"` string, ### pagy_countless(collection, vars=nil) -This method is the same as the generic `pagy` method. (see the [pagy doc](../api/backend.md#pagycollection-varsnil)) +This method is the same as the generic `pagy` method (see the [pagy doc](../api/backend.md#pagycollection-varsnil)), however its returned objects will depend on the value of the `:countless_minimal` variable (see [Modes](#modes)) ### pagy_countless_get_vars(_collection, vars) @@ -44,6 +78,3 @@ This sub-method is similar to the `pagy_get_vars` sub-method, but it is called o ### pagy_countless_get_items(collection, pagy) This sub-method is similar to the `pagy_get_items` sub-method, but it is called only by the `pagy_countless` method. (see the [pagy_get_items doc](../api/backend.md#pagy_get_itemscollection-pagy)). - -**Notice**: This method calls `to_a` on the collection in order to `pop` the eventual extra item from the result, so it returns an `Array`. That's different than the regular `pagy_get_items` method which doesn't need to call `to_a` on the collection. - diff --git a/lib/config/pagy.rb b/lib/config/pagy.rb index 3a41adfe0..008da4be0 100644 --- a/lib/config/pagy.rb +++ b/lib/config/pagy.rb @@ -42,6 +42,7 @@ # Countless extra: Paginate without any count, saving one query per rendering # See https://ddnexus.github.io/pagy/extras/countless # require 'pagy/extras/countless' +# Pagy::VARS[:countless_minimal] = false # default (eager loading) # Elasticsearch Rails extra: Paginate `ElasticsearchRails::Results` objects # See https://ddnexus.github.io/pagy/extras/elasticsearch_rails diff --git a/lib/pagy/extras/countless.rb b/lib/pagy/extras/countless.rb index d97ed705c..163004d43 100644 --- a/lib/pagy/extras/countless.rb +++ b/lib/pagy/extras/countless.rb @@ -5,6 +5,8 @@ class Pagy + VARS[:countless_minimal] = false + module Backend private # the whole module is private so no problem with including it in a controller @@ -17,13 +19,15 @@ def pagy_countless(collection, vars={}) # Sub-method called only by #pagy_countless: here for easy customization of variables by overriding def pagy_countless_get_vars(_collection, vars) pagy_set_items_from_params(vars) if defined?(UseItemsExtra) - vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ] + vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ] vars end # Sub-method called only by #pagy_countless: here for easy customization of record-extraction by overriding def pagy_countless_get_items(collection, pagy) # This should work with ActiveRecord, Sequel, Mongoid... + return collection.offset(pagy.offset).limit(pagy.items) if pagy.vars[:countless_minimal] + items = collection.offset(pagy.offset).limit(pagy.items + 1).to_a items_size = items.size items.pop if items_size == pagy.items + 1 diff --git a/test/pagy/extras/metadata_test.rb.rematch b/test/pagy/extras/metadata_test.rb.rematch index 8b07ee703..7db8c9934 100644 --- a/test/pagy/extras/metadata_test.rb.rematch +++ b/test/pagy/extras/metadata_test.rb.rematch @@ -1,30 +1,4 @@ --- -"[1] pagy/extras/metadata::#pagy_metadata#test_0004_returns only specific metadata": - :scaffold_url: "/foo?page=__pagy_page__" - :page: 3 - :count: 1000 - :prev: 2 - :next: 4 - :pages: 50 -"[1] pagy/extras/metadata::#pagy_metadata#test_0001_defines all metadata": -- :scaffold_url -- :first_url -- :prev_url -- :page_url -- :next_url -- :last_url -- :count -- :page -- :items -- :vars -- :pages -- :last -- :from -- :to -- :prev -- :next -- :series -- :sequels "[1] pagy/extras/metadata::#pagy_metadata#test_0002_returns the full pagy metadata": :scaffold_url: "/foo?page=__pagy_page__" :first_url: "/foo?page=1" @@ -50,6 +24,7 @@ :link_extra: '' :i18n_key: pagy.item_name :cycle: false + :countless_minimal: false :steps: false :metadata: - :scaffold_url @@ -98,3 +73,29 @@ - 7 - :gap - 50 +"[1] pagy/extras/metadata::#pagy_metadata#test_0001_defines all metadata": +- :scaffold_url +- :first_url +- :prev_url +- :page_url +- :next_url +- :last_url +- :count +- :page +- :items +- :vars +- :pages +- :last +- :from +- :to +- :prev +- :next +- :series +- :sequels +"[1] pagy/extras/metadata::#pagy_metadata#test_0004_returns only specific metadata": + :scaffold_url: "/foo?page=__pagy_page__" + :page: 3 + :count: 1000 + :prev: 2 + :next: 4 + :pages: 50