Class: ActiveRecordCursorPaginate::Paginator

Inherits:
Object
  • Object
show all
Defined in:
lib/activerecord_cursor_paginate/paginator.rb

Overview

Use this Paginator class to effortlessly paginate through ActiveRecord relations using cursor pagination.

Examples:

Iterating one page at a time

ActiveRecordCursorPaginate::Paginator
  .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ")
  .fetch

Iterating over the whole relation

paginator = ActiveRecordCursorPaginate::Paginator
              .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ")

# Will lazily iterate over the pages.
paginator.pages.each do |page|
  # do something with the page
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(relation, before: nil, after: nil, limit: nil, order: nil, append_primary_key: true, nullable_columns: nil, forward_pagination: before.nil?) ⇒ Paginator

Create a new instance of the ActiveRecordCursorPaginate::Paginator

Parameters:

  • relation (ActiveRecord::Relation)

    Relation that will be paginated.

  • before (String, nil) (defaults to: nil)

    Cursor to paginate upto (excluding).

  • after (String, nil) (defaults to: nil)

    Cursor to paginate forward from.

  • limit (Integer, nil) (defaults to: nil)

    Number of records to return in pagination.

  • order (Symbol, String, nil, Array<Symbol, String>, Hash) (defaults to: nil)

    Column(s) to order by, optionally with directions (either :asc or :desc, defaults to :asc). If none is provided, will default to ID column. NOTE: this will cause the query to filter on both the given column as well as the ID column. So you might want to add a compound index to your database similar to: “‘sql

    CREATE INDEX <index_name> ON <table_name> (<order_fields>..., id)
    

    “‘

  • append_primary_key (Boolean) (defaults to: true)

    (true). Specifies whether the primary column(s) should be implicitly appended to the list of sorting columns. It may be useful to disable it for the table with a UUID primary key or when the sorting is done by a combination of columns that are already unique.

  • nullable_columns (Symbol, String, nil, Array) (defaults to: nil)

    Columns which are nullable. By default, all columns are considered as non-nullable, if not in this list. 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.

  • forward_pagination (Boolean) (defaults to: before.nil?)

    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.

Raises:

  • (ArgumentError)

    If any parameter is not valid



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 55

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

  @relation = relation
  @primary_key = @relation.primary_key
  @append_primary_key = append_primary_key

  @cursor = @current_cursor = nil
  @forward_pagination = forward_pagination
  @before = @after = nil
  @page_size = nil
  @limit = nil
  @columns = []
  @directions = []
  @order = nil

  self.before = before
  self.after = after
  self.limit = limit
  self.order = order
  self.nullable_columns = nullable_columns
end

Instance Attribute Details

#afterObject

Returns the value of attribute after.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def after
  @after
end

#append_primary_keyObject (readonly)

Returns the value of attribute append_primary_key.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def append_primary_key
  @append_primary_key
end

#beforeObject

Returns the value of attribute before.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def before
  @before
end

#forward_paginationObject

Returns the value of attribute forward_pagination.



23
24
25
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 23

def forward_pagination
  @forward_pagination
end

#limitObject

Returns the value of attribute limit.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def limit
  @limit
end

#orderObject

Returns the value of attribute order.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def order
  @order
end

#relationObject (readonly)

Returns the value of attribute relation.



22
23
24
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 22

def relation
  @relation
end

Instance Method Details

#fetchActiveRecordCursorPaginate::Page Also known as: page

Note:

Calling this method advances the paginator.

Get the paginated result.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 137

def fetch
  relation = build_cursor_relation(@current_cursor)

  relation = relation.limit(@page_size + 1)
  records_plus_one = relation.to_a
  has_additional = records_plus_one.size > @page_size

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

  if @forward_pagination
    has_next_page = has_additional
    has_previous_page = @current_cursor.present?
  else
    has_next_page = @current_cursor.present?
    has_previous_page = has_additional
  end

  page = Page.new(
    records,
    order_columns: cursor_column_names,
    has_next: has_next_page,
    has_previous: has_previous_page,
    nullable_columns: nullable_cursor_column_names
  )

  advance_by_page(page) unless page.empty?

  page
end

#nullable_columns=(value) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 117

def nullable_columns=(value)
  value = Array.wrap(value)
  value = value.map { |column| column.is_a?(Symbol) ? column.to_s : column }

  if (value - @columns).any?
    raise ArgumentError, ":nullable_columns should include only column names from the :order option"
  end

  if value.include?(@columns.last)
    raise ArgumentError, "Last order column can not be nullable"
  end

  @nullable_columns = value
end

#pagesEnumerator

Returns an enumerator that can be used to iterate over the whole relation.

Returns:

  • (Enumerator)


172
173
174
175
176
177
178
179
180
181
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 172

def pages
  Enumerator.new do |yielder|
    loop do
      page = fetch
      break if page.empty?

      yielder.yield(page)
    end
  end
end

#total_countInteger

Total number of records to iterate by this paginator.

Returns:

  • (Integer)


186
187
188
# File 'lib/activerecord_cursor_paginate/paginator.rb', line 186

def total_count
  @total_count ||= @relation.count(:all)
end