class Pagy::Keyset

Implement wicked-fast keyset pagination for big data

def self.new(set, **vars)

Pick the right adapter for the set
def self.new(set, **vars)
  if self == Pagy::Keyset
    if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
      ActiveRecord
    elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
      Sequel
    else
      raise TypeError, "expected set to be an instance of ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
    end.new(set, **vars)
  else
    allocate.tap { |instance| instance.send(:initialize, set, **vars) }
  end
end

def filter_newest_query

( "pets"."animal", "pets"."name", "pets"."id" ) > ( :animal, :name, :id )
with a set like Pet.order(:animal, :name, :id) it returns the following string:
When :tuple_comparison is enabled, and if the order is all :asc or all :desc,
( "pets"."animal" > :animal )
( "pets"."animal" = :animal AND "pets"."name" < :name ) OR
( "pets"."animal" = :animal AND "pets"."name" = :name AND "pets"."id" > :id ) OR
With a set like Pet.order(animal: :asc, name: :desc, id: :asc) it returns the following string:
For example:
used to filter the newest records.
Prepare the literal query string (complete with the placeholders for value interpolation)
def filter_newest_query
  operator   = { asc: '>', desc: '<' }
  directions = @keyset.values
  table      = @set.model.table_name
  name       = @keyset.to_h { |column| [column, %("#{table}"."#{column}")] }
  if @vars[:tuple_comparison] && (directions.all?(:asc) || directions.all?(:desc))
    placeholders = @keyset.keys.map { |column| ":#{column}" }.join(', ')
    "( #{name.values.join(', ')} ) #{operator[directions.first]} ( #{placeholders} )"
  else
    keyset = @keyset.to_a
    where  = []
    until keyset.empty?
      last_column, last_direction = keyset.pop
      query = +'( '
      query << (keyset.map { |column, _d| "#{name[column]} = :#{column}" } \
                << "#{name[last_column]} #{operator[last_direction]} :#{last_column}").join(' AND ')
      query << ' )'
      where << query
    end
    where.join(' OR ')
  end
end

def initialize(set, **vars)

def initialize(set, **vars)
  default = DEFAULT.slice(:limit, :page_param,                    # from pagy
                          :headers,                               # from headers extra
                          :jsonapi,                               # from jsonapi extra
                          :limit_param, :limit_max, :limit_extra) # from limit_extra
  assign_vars({ **default, page: nil }, vars)
  assign_limit
  @set    = set
  @page   = @vars[:page]
  @keyset = extract_keyset
  raise InternalError, 'the set must be ordered' if @keyset.empty?
  return unless @page
  latest  = JSON.parse(B64.urlsafe_decode(@page)).transform_keys(&:to_sym)
  @latest = typecast_latest(latest)
  raise InternalError, 'page and keyset are not consistent' \
        unless @latest.keys == @keyset.keys
end

def next

Return the next page
def next
  records
  return unless @more
  @next ||= begin
              hash = keyset_attributes_from(@records.last)
              json = @vars[:jsonify_keyset_attributes]&.(hash) || hash.to_json
              B64.urlsafe_encode(json)
            end
end

def records

Fetch the array of records for the current page
def records
  @records ||= begin
                 @set = apply_select if select?
                 if @latest
                   # :nocov:
                   @set = @vars[:after_latest]&.(@set, @latest) || # deprecated
                          # :nocov:
                          @vars[:filter_newest]&.(@set, @latest, @keyset) ||
                          filter_newest
                 end
                 records = @set.limit(@limit + 1).to_a
                 @more   = records.size > @limit && !records.pop.nil?
                 records
               end
end