Module: Wurk::API::Page
- Defined in:
- lib/wurk/api/page.rb
Overview
Offset paging for the observe plane's listings.
Deliberately not the dashboard's Wurk::Api::Pagination. That module is autoloaded out of app/ by the engine, and the API has to page in standalone mode where Rails is never loaded (CLAUDE.md); requiring an autoloadable file by hand is exactly what Zeitwerk forbids. The contracts differ too — the dashboard's may change whenever the SPA does, everything under /v1 may not — so one shared module would tie a frozen contract to a mutable one.
Defined Under Namespace
Classes: Window
Constant Summary collapse
- DEFAULT_COUNT =
25- MAX_COUNT =
200- MAX_PAGE =
Offset paging reaches page N by walking N*count members through Ruby, and their Redis round trips with them, so an unclamped
pagelets one request drag an entire million-row set through this process. A thousand pages is past any real client's depth and still bounds the worst case. 1_000
Class Method Summary collapse
-
.integer!(raw, name, default) ⇒ Object
A repeated parameter (
?page=1&page=2) parses to an Array, which is the TypeError arm — an ambiguous request, answered rather than guessed. -
.query!(request) ⇒ Object
Rack::Request#params merges the POST body in, which the paged routes must never read.
-
.slice(enumerable, window) ⇒ Object
Walks
enumerable, skipswindow.offsetmembers, and hands at mostwindow.countof them to the block, which returns the row to emit. -
.window(query) ⇒ Object
The parsed-query half, for a route that reads a parameter of its own (
?filter=) beside the paging ones: it parses once and hands the same Hash to both, rather than re-parsing the query string per parameter. - .window!(request) ⇒ Object
Class Method Details
.integer!(raw, name, default) ⇒ Object
A repeated parameter (?page=1&page=2) parses to an Array, which is
the TypeError arm — an ambiguous request, answered rather than guessed.
68 69 70 71 72 73 74 |
# File 'lib/wurk/api/page.rb', line 68 def integer!(raw, name, default) return default if raw.nil? || raw == '' Integer(raw, 10) rescue ::ArgumentError, ::TypeError raise Validation::Invalid, "The '#{name}' parameter must be an integer." end |
.query!(request) ⇒ Object
Rack::Request#params merges the POST body in, which the paged routes
must never read. parse_query is also flat: ?page[]=1 arrives as the
key it literally is instead of nesting into a Hash that reaches
Integer().
Rack's own refusals (InvalidParameterError on bad %-encoding, QueryLimitError on an oversized one) are ArgumentError and RangeError respectively; caught here so a malformed query string is the 400 it is rather than the 500 App's catch-all would make of it.
60 61 62 63 64 |
# File 'lib/wurk/api/page.rb', line 60 def query!(request) ::Rack::Utils.parse_query(request.query_string) rescue ::ArgumentError, ::TypeError, ::RangeError raise Validation::Invalid, 'The query string could not be parsed.' end |
.slice(enumerable, window) ⇒ Object
Walks enumerable, skips window.offset members, and hands at most
window.count of them to the block, which returns the row to emit.
Skipped members are never yielded, so nothing before the offset is serialized: on a queue page that is a JobRecord whose JSON is never parsed, which is most of the cost of a deep page.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/wurk/api/page.rb', line 82 def slice(enumerable, window) rows = [] skipped = 0 enumerable.each do |member| if skipped < window.offset skipped += 1 next end rows << yield(member) break if rows.size >= window.count end rows end |
.window(query) ⇒ Object
The parsed-query half, for a route that reads a parameter of its own
(?filter=) beside the paging ones: it parses once and hands the same
Hash to both, rather than re-parsing the query string per parameter.
44 45 46 47 48 49 |
# File 'lib/wurk/api/page.rb', line 44 def window(query) Window.new( page: integer!(query['page'], 'page', 0).clamp(0, MAX_PAGE), count: integer!(query['count'], 'count', DEFAULT_COUNT).clamp(1, MAX_COUNT) ) end |
.window!(request) ⇒ Object
39 |
# File 'lib/wurk/api/page.rb', line 39 def window!(request) = window(query!(request)) |