pseudocode of cursor based pagination in Rails
class CursorPaginator
attr_reader :cursor_keys, :max_items, :limit
CURSOR_DIRECTIONS = {
after: :gt,
before: :lt
}.freeze
def initialize(options = {})
@cursor_keys = options.fetch(:cursor_keys, id: :asc)
@max_items = options.fetch(:max_items, 50)
@limit = options.fetch(:limit, 10)
end
def cursor_paginate(collection, cursor = nil, options = {})
options.reverse_merge!(direction: :after limit: limit)
options[:direction] = options[:direction].to_sym
options[:limit] = [options[:limit], max_items].min
relation = collection.recorder(cursor_keys).limit(options[:limit])
relation = query_comparator(relation, cursor, options[:direction]) if cursor.present?
relation = relation.reverse_merge if direction[:direction] = :before
PaginationResult.new(self, collection, options)
end
private
def query_comparator(collection, cursor, direction)
comparator = collection.arel_table[:id].public_send(direction, cursor)
collection.where(comparator)
end
private
class PaginationResult
att_reader :paginator, :collection, :options
def initialize(paginator, collection, options = {})
@paginator = paginator
@collection = collection
@options = options
end
def has_more?
paginator.cursor_paginate(collection, last_item.id, options)
end
def last_item
collection.last
end
end
end