summaryrefslogtreecommitdiff
path: root/lib/gitlab/pagination/keyset/pager.rb
blob: 99b125cc2a092715e2a214a690d0644ac1d6d236 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# frozen_string_literal: true

module Gitlab
  module Pagination
    module Keyset
      class Pager
        attr_reader :request

        def initialize(request)
          @request = request
        end

        def paginate(relation)
          # Validate assumption: The last two columns must match the page order_by
          validate_order!(relation)

          # This performs the database query and retrieves records
          # We retrieve one record more to check if we have data beyond this page
          all_records = relation.limit(page.per_page + 1).to_a # rubocop: disable CodeReuse/ActiveRecord

          records_for_page = all_records.first(page.per_page)

          # If we retrieved more records than belong on this page,
          # we know there's a next page
          there_is_more = all_records.size > records_for_page.size
          apply_headers(records_for_page.last, there_is_more)

          records_for_page
        end

        private

        def apply_headers(last_record_in_page, there_is_more)
          end_reached = last_record_in_page.nil? || !there_is_more
          lower_bounds = last_record_in_page&.slice(page.order_by.keys)

          next_page = page.next(lower_bounds, end_reached)

          request.apply_headers(next_page)
        end

        def page
          @page ||= request.page
        end

        def validate_order!(rel)
          present_order = rel.order_values.map { |val| [val.expr.name.to_sym, val.direction] }.last(2).to_h

          unless page.order_by == present_order
            raise ArgumentError, "Page's order_by does not match the relation's order: #{present_order} vs #{page.order_by}"
          end
        end
      end
    end
  end
end