Module: CleanPagination

Defined in:
lib/clean_pagination.rb,
lib/clean_pagination/version.rb

Constant Summary collapse

VERSION =
"1.0.0"

Instance Method Summary collapse

Instance Method Details

#paginate(total_items, max_range_size, options = {}) {|available_limit, requested_from| ... } ⇒ Object

Yields:

  • (available_limit, requested_from)


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
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
# File 'lib/clean_pagination.rb', line 3

def paginate total_items, max_range_size, options = {}
  options[:allow_render] = true if options[:allow_render].nil?
  options[:raise_errors] ||= false

  headers['Accept-Ranges'] = 'items'
  headers['Range-Unit'] = 'items'

  requested_from, requested_to = 0, [0, total_items - 1].max

  if request.headers['Range-Unit'] == 'items' &&
     request.headers['Range'].present?
    if request.headers['Range'] =~ /(\d+)-(\d*)/
      requested_from, requested_to = $1.to_i, ($2.present? ? $2.to_i : Float::INFINITY)
    end
  end

  if (requested_from > requested_to) ||
     (requested_from > 0 && requested_from >= total_items)
    response.status = 416
    headers['Content-Range'] = "*/#{total_items}"
    message = 'invalid pagination range'
    raise RangeError, message if options[:raise_errors]
    render text: message if options[:allow_render]
    return
  end

  available_to = [requested_to,
                  total_items - 1,
                  requested_from + max_range_size - 1
                 ].min
  available_limit = available_to - requested_from + 1

  if available_limit == 0
    headers['Content-Range'] = "*/0"
  else
    headers['Content-Range'] = "#{
        requested_from
      }-#{
        available_to
      }/#{
        total_items < Float::INFINITY ? total_items : '*'
      }"
  end

  yield available_limit, requested_from
  if available_limit < total_items && response.status == 200
    response.status = 206
  end

  requested_limit = requested_to - requested_from + 1

  links = []
  if available_to < total_items - 1
    links << "<#{request.url}>; rel=\"next\"; items=\"#{
        available_to + 1
      }-#{
        suppress_infinity(available_to + requested_limit)
      }\""

    if total_items < Float::INFINITY
      links << "<#{request.url}>; rel=\"last\"; items=\"#{
        # let rounding do the work
        ((total_items-1) / available_limit) * available_limit
      }-#{
        (((total_items-1) / available_limit) * available_limit) + requested_limit - 1
      }\""
    end
  end
  if requested_from > 0
    previous_from = [0, requested_from - [requested_limit, max_range_size].min].max

    links << "<#{request.url}>; rel=\"prev\"; items=\"#{
        previous_from
      }-#{
        suppress_infinity(previous_from + requested_limit - 1)
      }\""

    links << "<#{request.url}>; rel=\"first\"; items=\"0-#{suppress_infinity(requested_limit-1)}\""
  end

  headers['Link'] = links.join ', ' unless links.empty?
end