Skip to content

Commit

Permalink
Add ability to paginate backward from the end of the collection
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Jan 18, 2025
1 parent 452c8b4 commit fbff084
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## master (unreleased)

- Add ability to paginate backward from the end of the collection

```ruby
paginator = users.cursor_paginate(forward_pagination: false)
```

## 0.3.0 (2025-01-06)

- Allow paginating over nullable columns
Expand Down
26 changes: 16 additions & 10 deletions lib/activerecord_cursor_paginate/paginator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module ActiveRecordCursorPaginate
#
class Paginator
attr_reader :relation, :before, :after, :limit, :order, :append_primary_key
attr_accessor :forward_pagination

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

@cursor = @current_cursor = nil
@is_forward_pagination = true
@forward_pagination = forward_pagination
@before = @after = nil
@page_size = nil
@limit = nil
Expand All @@ -80,17 +85,18 @@ def before=(value)

@cursor = value || after
@current_cursor = @cursor
@is_forward_pagination = value.blank?
@forward_pagination = false if value
@before = value
end

def after=(value)
if before.present? && value.present?
if value.present? && before.present?
raise ArgumentError, "Only one of :before and :after can be provided"
end

@cursor = before || value
@cursor = value || before
@current_cursor = @cursor
@forward_pagination = true if value
@after = value
end

Expand Down Expand Up @@ -136,9 +142,9 @@ def fetch
has_additional = records_plus_one.size > @page_size

records = records_plus_one.take(@page_size)
records.reverse! unless @is_forward_pagination
records.reverse! unless @forward_pagination

if @is_forward_pagination
if @forward_pagination
has_next_page = has_additional
has_previous_page = @current_cursor.present?
else
Expand Down Expand Up @@ -301,15 +307,15 @@ def arel_column(column)
end

def pagination_direction(direction)
if @is_forward_pagination
if @forward_pagination
direction
else
direction == :asc ? :desc : :asc
end
end

def pagination_operator(direction)
if @is_forward_pagination
if @forward_pagination
direction == :asc ? :gt : :lt
else
direction == :asc ? :lt : :gt
Expand All @@ -318,7 +324,7 @@ def pagination_operator(direction)

def advance_by_page(page)
@current_cursor =
if @is_forward_pagination
if @forward_pagination
page.next_cursor
else
page.previous_cursor
Expand Down
10 changes: 10 additions & 0 deletions test/paginator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ def test_paginating_by_multiple_nullable_cursor_columns_in_desc_order
assert_equal Project.order(name: :desc, organization_id: :asc, id: :asc).to_a, records
end

def test_paginating_backward_from_the_end
p = User.cursor_paginate(limit: 3, forward_pagination: false)

ids = p.pages.map do |page|
page.records.pluck(:id)
end

assert_equal [[8, 9, 10], [5, 6, 7], [2, 3, 4], [1]], ids
end

def test_uses_default_limit
ActiveRecordCursorPaginate.config.stub(:default_page_size, 4) do
p = User.cursor_paginate
Expand Down

0 comments on commit fbff084

Please sign in to comment.