Module: PageCursor::ActionControllerExtension

Defined in:
lib/page_cursor/cursor.rb

Instance Method Summary collapse

Instance Method Details

#paginate(c, direction = nil, **opts) ⇒ Object

paginate returns a cursor-enabled pagination. It uses params and params request variables. It assumes @record’s primary key is sortable.

opts = string opts = n

Raises:

  • (ArgumentError)


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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/page_cursor/cursor.rb', line 9

def paginate(c, direction = nil, **opts)
  opts.symbolize_keys!
  limit = opts[:limit]&.to_i || 10

  raise ArgumentError, "direction must be either nil, :asc or :desc" unless [nil, :asc, :desc].include?(direction)
  raise ArgumentError, "limit must be >= 1" unless limit >= 1
  raise ArgumentError, "only provide one, either params[:after] or params[:before]" if params[:after].present? && params[:before].present?

  # make sure we have a primary key
  pk_name = (opts[:primary_key] || c.primary_key).to_s
  if !c.column_names.include?(pk_name)
    if opts[:primary_key].present?
      raise ArgumentError, "column '#{opts[:primary_key]}' does not exist in table '#{c.table_name}'"
    else
      raise "table '#{c.table_name}' has no primary key"
    end
  end

  # reference the table's primary key
  pk = c.arel_table[pk_name]
  raise ArgumentError, "expect primary key to be Arel::Attributes:Attribute instead of #{pk.class}" unless pk.is_a?(Arel::Attributes::Attribute)

  # set cursor to :after/:before and the according pk_value from the params
  cursor = nil
  pk_value = nil
  if params[:after].present?
    cursor = :after
    pk_value = params[:after]
  elsif params[:before].present?
    cursor = :before
    pk_value = params[:before]
  end

  # always fetch limit + 1 to see if there are more records
  c = c.limit(limit + 1)

  all = []

  # check if c already has one or more order directives set
  unless already_has_order?(c)
    # easy, no existing order directives, we'll just order by our primary key
    comparison, order, reverse = ordering(direction || :asc, cursor)
    c = c.where(pk.send(comparison, pk_value)) if comparison
    c = c.reorder(pk.send(order)).all
    c = c.reverse if reverse
    all = c.to_a
  else
    # collection has order directives, we need to do a bit more work ...

    # replace existing order with new one
    c = reorder(c, cursor, pk, direction || :asc)

    # if a cursor is given, we need to fetch its row from the database
    # so that we can use the row's values for our where conditions.
    unless cursor.nil?
      row = find!(c, pk_name, pk_value)
      c = where(c, cursor, row)
    end

    all = c.all.to_a
    all = all.reverse if cursor == :before
  end

  has_more = all.size <= limit ? false : true

  # return new after/before cursor and all results if there are no more results to expect after this
  unless has_more
    if cursor.nil?
      return { :after => nil, :before => nil }, all # first and only page, no afters/befores
    elsif cursor == :after
      return { :after => nil, :before => all.first&.read_attribute(pk_name) }, all  # last page, no afters
    elsif cursor == :before
      return { :after => all.last&.read_attribute(pk_name), :before => nil }, all  # last page, no befores
    end
  end

  # return new after/before cursors and all results if there are more results to expect
  if cursor == :before
    all = all.last(all.size - 1)
  else
    all = all.first(all.size - 1)
  end

  if cursor.nil?
    return { :after => all.last&.read_attribute(pk_name), :before => nil }, all # first page, continue after
  elsif cursor == :after
    return { :after => all.last&.read_attribute(pk_name), :before => all.first&.read_attribute(pk_name) }, all
  elsif cursor == :before
    return { :after => all.last&.read_attribute(pk_name), :before => all.first&.read_attribute(pk_name) }, all
  end

  fail "never" # safeguard if cursor has a weird value
end