ruby 42 lines · 1 tab

Pagination with cursor-based approach

Alex Kumar Jan 2026
1 tab
module CursorPagination
  extend ActiveSupport::Concern

  private

  def paginate_with_cursor(scope, per_page: 20)
    cursor = params[:cursor]
    direction = params[:direction] || 'next'

    query = scope.limit(per_page + 1)

    if cursor.present?
      decoded_cursor = decode_cursor(cursor)
      if direction == 'next'
        query = query.where('id > ?', decoded_cursor)
      else
        query = query.where('id < ?', decoded_cursor).reverse_order
      end
    end

    records = query.to_a
    has_more = records.size > per_page
    records = records.take(per_page)

    {
      data: records,
      meta: {
        next_cursor: has_more ? encode_cursor(records.last.id) : nil,
        prev_cursor: cursor.present? ? encode_cursor(records.first.id) : nil,
        has_more: has_more
      }
    }
  end

  def encode_cursor(id)
    Base64.strict_encode64(id.to_s)
  end

  def decode_cursor(cursor)
    Base64.strict_decode64(cursor).to_i
  end
end
1 file · ruby Explain with highlit

Traditional offset-based pagination becomes unreliable and slow for large datasets when records are frequently inserted or deleted—users can miss items or see duplicates across pages. Cursor-based pagination solves this by using an opaque token that encodes the position in the dataset, typically the id of the last seen record. Each response includes next_cursor and prev_cursor fields that clients pass back to fetch subsequent pages. This approach delivers consistent results even when the underlying data changes between requests, and it performs well at scale because it uses indexed lookups rather than counting offsets. I encode cursors with Base64 to hide implementation details and include pagination metadata in a top-level meta object.


Related snips

Share this code

Here's the card — post it anywhere.

Pagination with cursor-based approach — share card
Link copied