Skip to content

Commit fbff084

Browse files
committed
Add ability to paginate backward from the end of the collection
1 parent 452c8b4 commit fbff084

File tree

3 files changed

+32
-10
lines changed

3 files changed

+32
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
## master (unreleased)
22

3+
- Add ability to paginate backward from the end of the collection
4+
5+
```ruby
6+
paginator = users.cursor_paginate(forward_pagination: false)
7+
```
8+
39
## 0.3.0 (2025-01-06)
410

511
- Allow paginating over nullable columns

lib/activerecord_cursor_paginate/paginator.rb

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module ActiveRecordCursorPaginate
2020
#
2121
class Paginator
2222
attr_reader :relation, :before, :after, :limit, :order, :append_primary_key
23+
attr_accessor :forward_pagination
2324

2425
# Create a new instance of the `ActiveRecordCursorPaginate::Paginator`
2526
#
@@ -45,10 +46,14 @@ class Paginator
4546
# It is not recommended to use this feature, because the complexity of produced SQL
4647
# queries can have a very negative impact on the database performance. It is better
4748
# to paginate using only non-nullable columns.
49+
# @param forward_pagination [Boolean] Whether this is a forward or backward pagination.
50+
# Optional, defaults to `true` if `:before` is not provided, `false` otherwise.
51+
# Useful when paginating backward from the end of the collection.
4852
#
4953
# @raise [ArgumentError] If any parameter is not valid
5054
#
51-
def initialize(relation, before: nil, after: nil, limit: nil, order: nil, append_primary_key: true, nullable_columns: nil)
55+
def initialize(relation, before: nil, after: nil, limit: nil, order: nil, append_primary_key: true,
56+
nullable_columns: nil, forward_pagination: before.nil?)
5257
unless relation.is_a?(ActiveRecord::Relation)
5358
raise ArgumentError, "relation is not an ActiveRecord::Relation"
5459
end
@@ -58,7 +63,7 @@ def initialize(relation, before: nil, after: nil, limit: nil, order: nil, append
5863
@append_primary_key = append_primary_key
5964

6065
@cursor = @current_cursor = nil
61-
@is_forward_pagination = true
66+
@forward_pagination = forward_pagination
6267
@before = @after = nil
6368
@page_size = nil
6469
@limit = nil
@@ -80,17 +85,18 @@ def before=(value)
8085

8186
@cursor = value || after
8287
@current_cursor = @cursor
83-
@is_forward_pagination = value.blank?
88+
@forward_pagination = false if value
8489
@before = value
8590
end
8691

8792
def after=(value)
88-
if before.present? && value.present?
93+
if value.present? && before.present?
8994
raise ArgumentError, "Only one of :before and :after can be provided"
9095
end
9196

92-
@cursor = before || value
97+
@cursor = value || before
9398
@current_cursor = @cursor
99+
@forward_pagination = true if value
94100
@after = value
95101
end
96102

@@ -136,9 +142,9 @@ def fetch
136142
has_additional = records_plus_one.size > @page_size
137143

138144
records = records_plus_one.take(@page_size)
139-
records.reverse! unless @is_forward_pagination
145+
records.reverse! unless @forward_pagination
140146

141-
if @is_forward_pagination
147+
if @forward_pagination
142148
has_next_page = has_additional
143149
has_previous_page = @current_cursor.present?
144150
else
@@ -301,15 +307,15 @@ def arel_column(column)
301307
end
302308

303309
def pagination_direction(direction)
304-
if @is_forward_pagination
310+
if @forward_pagination
305311
direction
306312
else
307313
direction == :asc ? :desc : :asc
308314
end
309315
end
310316

311317
def pagination_operator(direction)
312-
if @is_forward_pagination
318+
if @forward_pagination
313319
direction == :asc ? :gt : :lt
314320
else
315321
direction == :asc ? :lt : :gt
@@ -318,7 +324,7 @@ def pagination_operator(direction)
318324

319325
def advance_by_page(page)
320326
@current_cursor =
321-
if @is_forward_pagination
327+
if @forward_pagination
322328
page.next_cursor
323329
else
324330
page.previous_cursor

test/paginator_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ def test_paginating_by_multiple_nullable_cursor_columns_in_desc_order
8080
assert_equal Project.order(name: :desc, organization_id: :asc, id: :asc).to_a, records
8181
end
8282

83+
def test_paginating_backward_from_the_end
84+
p = User.cursor_paginate(limit: 3, forward_pagination: false)
85+
86+
ids = p.pages.map do |page|
87+
page.records.pluck(:id)
88+
end
89+
90+
assert_equal [[8, 9, 10], [5, 6, 7], [2, 3, 4], [1]], ids
91+
end
92+
8393
def test_uses_default_limit
8494
ActiveRecordCursorPaginate.config.stub(:default_page_size, 4) do
8595
p = User.cursor_paginate

0 commit comments

Comments
 (0)