samnang
3/10/2019 - 6:18 AM

pseudocode of cursor based pagination in Rails

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