Module: Wurk::Api::Pagination
- Defined in:
- app/controllers/wurk/api/pagination.rb
Overview
Pagination helpers shared by every listing endpoint. The contract:
* `?count=` page size (default 25, clamped to 1..200)
* `?page=` 0-indexed page number
* `?substr=` case-insensitive klass/jid filter on the page
Helpers expect an Enumerable that yields whatever JSON-shaped Hash the caller built; substr filtering and slicing happen after serialization so filters work on the same fields the UI reads.
Constant Summary collapse
- DEFAULT_PAGE_SIZE =
25- MAX_PAGE_SIZE =
200- MAX_PAGE =
Offset pagination reaches page N by walking N*count rows through Ruby (and their Redis round-trips). An unclamped ?page= lets one request walk an entire million-row set; 1000 pages is far beyond any real UI depth while bounding the worst-case walk.
1_000- FILTER_SCAN_LIMIT =
A substr that matches nothing would otherwise stream the whole backing set through Ruby (same DoS shape Web::Search bounds with SCAN_BUDGET). Raw rows examined per filtered request; results within the budget are exact, beyond it the page just comes back short.
20_000
Class Method Summary collapse
- .clamp_float(value, min, max, default) ⇒ Object
- .clamp_int(value, min, max, default) ⇒ Object
- .match?(payload, substr) ⇒ Boolean
-
.slice(enumerable, page) ⇒ Object
Iterates
enumerableand collects up tocountpayloads from the requested page after substr filtering. -
.slice_filtered(enumerable, offset, page) ⇒ Object
Filtered path: the offset counts MATCHING rows, so page boundaries stay stable (raw-index offsets re-emit or skip matches across pages).
-
.slice_raw(enumerable, offset, count) ⇒ Object
Fast path: skip serialization entirely for rows before the offset.
- .window(params) ⇒ Object
Class Method Details
.clamp_float(value, min, max, default) ⇒ Object
43 44 45 46 47 |
# File 'app/controllers/wurk/api/pagination.rb', line 43 def clamp_float(value, min, max, default) Float(value).clamp(min, max) rescue ::ArgumentError, ::TypeError default end |
.clamp_int(value, min, max, default) ⇒ Object
37 38 39 40 41 |
# File 'app/controllers/wurk/api/pagination.rb', line 37 def clamp_int(value, min, max, default) Integer(value, 10).clamp(min, max) rescue ::ArgumentError, ::TypeError default end |
.match?(payload, substr) ⇒ Boolean
108 109 110 111 112 113 |
# File 'app/controllers/wurk/api/pagination.rb', line 108 def match?(payload, substr) return true if substr.nil? || substr.empty? needle = substr.downcase payload[:klass].to_s.downcase.include?(needle) || payload[:jid].to_s.downcase.include?(needle) end |
.slice(enumerable, page) ⇒ Object
Iterates enumerable and collects up to count payloads from the
requested page after substr filtering. block maps each member to a
JSON Hash; nil from the block skips the member entirely.
With a substr filter the offset must count matching rows, not raw rows — otherwise page N restarts at raw index N*count and re-emits (or skips) matches already assigned to earlier pages. Without a filter we keep the fast path that skips serialization before the offset.
58 59 60 61 62 63 64 65 |
# File 'app/controllers/wurk/api/pagination.rb', line 58 def slice(enumerable, page, &) offset = page[:page] * page[:count] if page[:substr].empty? slice_raw(enumerable, offset, page[:count], &) else slice_filtered(enumerable, offset, page, &) end end |
.slice_filtered(enumerable, offset, page) ⇒ Object
Filtered path: the offset counts MATCHING rows, so page boundaries stay stable (raw-index offsets re-emit or skip matches across pages).
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'app/controllers/wurk/api/pagination.rb', line 88 def slice_filtered(enumerable, offset, page) results = [] matched = 0 examined = 0 enumerable.each do |member| examined += 1 break if examined > FILTER_SCAN_LIMIT payload = yield(member) next unless payload && match?(payload, page[:substr]) if matched >= offset results << payload break if results.size >= page[:count] end matched += 1 end results end |
.slice_raw(enumerable, offset, count) ⇒ Object
Fast path: skip serialization entirely for rows before the offset.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'app/controllers/wurk/api/pagination.rb', line 68 def slice_raw(enumerable, offset, count) results = [] seen = 0 enumerable.each do |member| if seen < offset seen += 1 next end payload = yield(member) next unless payload results << payload break if results.size >= count end results end |
.window(params) ⇒ Object
29 30 31 32 33 34 35 |
# File 'app/controllers/wurk/api/pagination.rb', line 29 def window(params) { page: clamp_int(params[:page], 0, MAX_PAGE, 0), count: clamp_int(params[:count], 1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE), substr: params[:substr].to_s } end |