Skip to content

Commit

Permalink
Reorganized from cut, @prev_cut and @next_cut to cutoff, @prev_cutoff,
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnexus committed Dec 14, 2024
1 parent 0707a86 commit e800c5a
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 129 deletions.
16 changes: 8 additions & 8 deletions docs/api/keyset.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ If you want the best of the two worlds, check out the [keyset_numeric extra](/do
| `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. |
| `cut` | A point in the `set` that separates the records of two contiguous `page`s. It's the encoded reference to the last record of a `page`. |
| `page` | The current `page`, i.e. the page of records beginning after the `prev_cut`. Also the `:page` variable, which is set to the `prev_cut` |
| `next` | The next `page`, i.e. the page of records beginning after the `next_cut`. Also the `next_cut` value retured by the `next` method. |
| `cutoff` | A point in the `set` that separates the records of two contiguous `page`s. It's the encoded reference to the last record of a `page`. |
| `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. |

### Keyset, Numeric or Offset pagination?

Expand Down Expand Up @@ -156,8 +156,8 @@ If you need a specific order:
### How Pagy::Keyset works

- You pass an `uniquely ordered` `set` and `Pagy::Keyset` pulls the `:limit` of records of the first page.
- It requests the `next` URL by setting its `page` query string param to the `next_cut` of the current page.
- At each request, the new `page` is decoded into `cut_args` that are coupled with a `where` filter query, and the `:limit` of new
- It requests the `next` URL by setting its `page` query string param to the `cutoff` of the current page.
- At each request, the new `page` is decoded into `cutoff_args` that are coupled with a `where` filter query, and the `:limit` of new
records is pulled.
- You know that you reached the end of the collection when `pagy.next.nil?`.

Expand All @@ -182,7 +182,7 @@ The constructor takes the `set`, and an optional hash of [variables](#variables)

==- `next`

The next `page`, i.e. the `cut` after the last record of the **current page**. It is `nil` for the last page.
The next `page`, i.e. the `cutoff` after the last record of the **current page**. It is `nil` for the last page.

==- `records`

Expand All @@ -194,14 +194,14 @@ The `Array` of fetched records for the current page.

=== `:page`

The current page, i.e. the `next_cut` of the **previous page**. Default `nil` for the first page.
The current page, i.e. the `cutoff` of the **previous page**. Default `nil` for the first page.

=== `:limit`

The `:limit` per page. Default `Pagy::DEFAULT[:limit]`. You can use the [limit extra](/docs/extras/limit.md) to have it
automatically assigned from the `limit` request param.

=== `:tuple_comparison`
==- `:tuple_comparison`

Boolean variable that enables the tuple comparison e.g. `(brand, id) > (:brand, :id)`. It works only with the same direction
order, hence it's ignored for mixed order. Check how your DB supports it (your `keyset` should include only `NOT NULL` columns).
Expand Down
26 changes: 13 additions & 13 deletions docs/api/keyset_numeric.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ Integrates the [Keyset Glossary](keyset_numeric.md#glossary)
|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `keyset numeric pagination` | The pagy exclusive technique to use `keyset pagination` with numeric pages, supporting `pagy_*navs` and the other Frontend helpers.<br/>The best technique for performance AND functionality! |
| `page` | The current page **number** |
| `cuts` | The `cut`s of the pagination known so far, used to keep track of the visited pages. |
| `cutoffs` | The `cutoff`s of the pagination known so far, used to keep track of the visited pages. |

## How Pagy::Keyset::Numeric works

### Caching the known cuts
### Caching the known cutoffs

`Pagy::Keyset::Numeric` keeps track of the state of the pagination using the `cuts`Array. As soon as a page is visited, its `next_cut` gets added to the `cuts` array (which is cached) hence it can be retrieved by numeric index in future requests.
`Pagy::Keyset::Numeric` keeps track of the state of the pagination using the `cutoffs`Array. As soon as a page is visited, its `cutoff` gets added to the `cutoffs` array (which is cached) hence it can be retrieved by numeric index in future requests.

While the `cuts` data must persist between requests for this class to work, this class does not handle the persistency at all: that is a concern of the [keyset_numeric extra](/docs/extras/keyset_numeric). Here is a simplified example of what must happen with the `cuts` at each request:
While the `cutoffs` data must persist between requests for this class to work, this class does not handle the persistency at all: that is a concern of the [keyset_numeric extra](/docs/extras/keyset_numeric). Here is a simplified example of what must happen with the `cutoffs` at each request:

```ruby
cuts = read_from_cache(cache_key)
pagy = Keyset::Numeric.new(set, cuts:, **vars)
write_to_cache(cache_key, pagy.cuts)
cutoffs = read_from_cache(cache_key)
pagy = Keyset::Numeric.new(set, cutoffs:, **vars)
write_to_cache(cache_key, pagy.cutoffs)
```

!!! Notice
Expand All @@ -60,7 +60,7 @@ The cache handling is external to the `Pagy::Keyset::Numeric` class for easy ove

### Numeric variables for the Frontend Helpers

Keeping track of the state through the `cuts` allows to set the numeric variables that the Frontend helpers require (see [Attribute Readers](#attribute-readers)).
Keeping track of the state through the `cutoffs` allows to set the numeric variables that the Frontend helpers require (see [Attribute Readers](#attribute-readers)).

However, it's still keyset pagination, which doesn't know any future page after the `next` page, so we add more pages on the go like we do with `Pagy::Countless`... just better for two reasons:

Expand All @@ -69,11 +69,11 @@ However, it's still keyset pagination, which doesn't know any future page after

### Accurate queries

`Pagy::Keyset::Numeric` knows all the `cuts` of the current pagination. If the `cuts` don't contain the `next_cut`, then it's a new requested page, and it proceeds exactly like the standard `Pagy::Keyset` class.
`Pagy::Keyset::Numeric` knows all the `cutoffs` of the current pagination. If the `cutoffs` don't contain the current page's `cutoff`, then it's a new requested page, and it proceeds exactly like the standard `Pagy::Keyset` class.

However, if the `cuts` contain the `next_cut` then it's already requested page, and from the time of the first request the number of records pulled with LIMIT may have changed for the page.
However, if the `cutoffs` contain the current page's `cutoff` then it's an already requested page, and the number of records pulled with LIMIT may have changed since the time of the first request.

Querying with the LIMIT again, might cause records to get skipped or to appear twice! So, to get it right, it pulls the records BETWEEN the `prev_cut` and `next_cut` avoiding the LIMIT and inluding the right records regardless.
Querying with the LIMIT again, might cause records to get skipped or to appear twice! So, to get it right, it pulls the records BETWEEN the `prev_cutoff` and the `cutoff` avoiding the LIMIT and inluding the right records regardless.

## Setup

Expand Down Expand Up @@ -110,7 +110,7 @@ The `Array` of fetched records for the current page.

This section ontegrates the [Pagy::Keyset variables](keyset.md#variables):

==- `:cuts`
==- `:cutoffs`

Mandatory array that must persist between requests.

Expand All @@ -126,7 +126,7 @@ Resets the pagination in case of overflow, instead of raising a `Pagy::OverflowE

## Attribute Readers

`cuts`, `in`, `last`, `limit`, `next`, `page`, `prev`, `vars`
`cutoffs`, `in`, `last`, `limit`, `next`, `page`, `prev`, `vars`

## Troubleshooting

Expand Down
4 changes: 2 additions & 2 deletions gem/lib/pagy/extras/keyset_numeric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def pagy_keyset_numeric(set, **vars)
vars[:limit] ||= pagy_get_limit(vars)
vars[:cache_key] ||= params[vars[:cache_key_param] || DEFAULT[:cache_key_param]] ||
pagy_cache_new_key
vars[:cuts] ||= pagy_cache_read(vars[:cache_key])
vars[:cutoffs] ||= pagy_cache_read(vars[:cache_key])

pagy = Keyset::Numeric.new(set, **vars)
pagy_cache_write(vars[:cache_key], pagy.cuts)
pagy_cache_write(vars[:cache_key], pagy.cutoffs)
[pagy, pagy.records]
end

Expand Down
36 changes: 18 additions & 18 deletions gem/lib/pagy/keyset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ def initialize(set, **vars)
raise InternalError, 'the set must be ordered' if @keyset.empty?

assign_page
assign_cuts
assign_cut_args
assign_cutoffs
assign_cutoff_args
end

# Assign the prev_cut from the page variable
def assign_cuts
@prev_cut = @vars[:page]
# Assign the prev_cutoff from the page variable
def assign_cutoffs
@prev_cutoff = @vars[:page]
end

# Assign the cut_args
def assign_cut_args
return unless @prev_cut
# Assign the cutoff_args
def assign_cutoff_args
return unless @prev_cutoff

@cut_args = cut_to_args(@prev_cut)
@cutoff_args = cutoff_to_args(@prev_cutoff)
end

# Assign the page
Expand All @@ -80,7 +80,7 @@ def assign_page
#
# ("pets"."animal", "pets"."name", "pets"."id") > (:animal, :name, :id)
#
def after_cut_sql(prefix = nil)
def after_cutoff_sql(prefix = nil)
operator = { asc: '>', desc: '<' }
directions = @keyset.values
table = @set.model.table_name
Expand All @@ -103,10 +103,10 @@ def after_cut_sql(prefix = nil)
end
end

# Decode a cut, check its consistency and returns the cut args
def cut_to_args(cut)
args = JSON.parse(B64.urlsafe_decode(cut)).transform_keys(&:to_sym)
raise InternalError, 'cut and keyset are not consistent' \
# 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

typecast_args(args)
Expand All @@ -121,8 +121,8 @@ def default
{ **default, page: nil }
end

# Derive the next_cut
def derive_next_cut
# 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
B64.urlsafe_encode(json)
Expand All @@ -140,14 +140,14 @@ def next
records
return unless @more

@next ||= derive_next_cut
@next ||= derive_cutoff
end

# Fetch the array of records for the current page
def records
@records ||= begin
@set = apply_select if select?
@set = filter_records if @cut_args
@set = filter_records if @cutoff_args
fetch_records
end
end
Expand Down
2 changes: 1 addition & 1 deletion gem/lib/pagy/keyset/active_record_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def extract_keyset
end

# Filter the page records
def filter_records = @set.where(after_cut_sql, **@cut_args)
def filter_records = @set.where(after_cutoff_sql, **@cutoff_args)

# Get the keyset attributes from the record
def keyset_attributes_from(record) = record.slice(*@keyset.keys)
Expand Down
Loading

0 comments on commit e800c5a

Please sign in to comment.