Class: Dinie::Page
- Inherits:
-
Object
- Object
- Dinie::Page
- Defined in:
- lib/dinie/runtime/paginator.rb,
sig/dinie/runtime/paginator.rbs
Overview
Items must respond to #id — the next page's starting_after cursor is the id of the
last item on the current page. Every list item in the frozen surface satisfies this.
Page — one page of a cursor-paginated list, and the auto-pagination engine over the pages
that follow it (architecture §11, RB8). Ports sdk-js src/runtime/paginator.ts and mirrors
the mechanics of OpenAI Ruby's base_page.rb. Part of the public surface (Dinie::Page).
Dinie has exactly ONE cursor scheme (inherited contract): starting_after (the id of the
last item of the previous page) + has_more (the ONLY end-of-list signal — never
data.length == limit, since the server may return fewer items with has_more: true).
A resource's list* does the first request eagerly and returns a Page carrying the
first page's data, its has_more, and a fetch_page closure that knows how to fetch the
next page (it maps a cursor to the starting_after query param and calls the transport).
The Page owns when to call the closure and what cursor to pass; the resource owns how
the closure reaches the network — so this class stays pure and is tested with an in-memory
fake (no HttpClient coupling).
── Why NOT include Enumerable (RB8) ──
Enumerable would graft on methods that materialize EVERY page eagerly (to_a, count,
sort, min…). A paginator must stay lazy, so we deliberately define only #each (+ its
#auto_paging_each alias) and #each_page; calling either without a block returns an
Enumerator, which gives .map / .first / .lazy on demand without eager-loading.
── Iteration modes ──
#each/#auto_paging_each— yield EVERY item of EVERY following page (auto-paginate).#each_page— yield page-by-page (manual control via#next_page?/#next_page).#first(n)— the firstnitems across pages, fetched lazily (stops as soon as it hasn).
Constant Summary collapse
- NO_NEXT_PAGE_MESSAGE =
Message raised by #next_page when there is no next page to fetch.
"No next page; guard with `#next_page?` (or check `#has_more`) first."- UNSPECIFIED =
Sentinel distinguishing
first(one item) fromfirst(n)(an array) —nil/0are validn. Object.new
Instance Attribute Summary collapse
-
#data ⇒ Array<Object>
readonly
Items on this page — already deserialized into typed POROs by the resource's
fetch_page. -
#has_more ⇒ Boolean
readonly
Whether the API reports more pages after this one — the ONLY end-of-list signal.
Class Method Summary collapse
-
.build(envelope, fetch_page) ⇒ void
Wrap a fetched
{ data:, has_more: }envelope in aPage. -
.from_fetch(fetch_page) ⇒ void
Build the FIRST page by invoking
fetch_pagewith no cursor. -
.paginated?(envelope) ⇒ Boolean
A list endpoint is paginated iff its envelope carries
has_more.
Instance Method Summary collapse
-
#each {|item| ... } ⇒ Object
(also: #auto_paging_each)
Auto-paginate: yield every item of this page, then every following page.
-
#each_page {|page| ... } ⇒ Object
Yield page-by-page (manual control).
-
#first(count = UNSPECIFIED) ⇒ Object
The first
countitems across pages (or the very first item), fetched lazily. -
#initialize(data:, has_more:, fetch_page:) ⇒ Page
constructor
A new instance of Page.
-
#next_page ⇒ Page[Elem]
Fetch the next page (cursor =
idof this page's last item). -
#next_page? ⇒ Boolean
Whether a next page can be fetched.
Constructor Details
#initialize(data:, has_more:, fetch_page:) ⇒ Page
Returns a new instance of Page.
85 86 87 88 89 90 |
# File 'lib/dinie/runtime/paginator.rb', line 85 def initialize(data:, has_more:, fetch_page:) @data = data @has_more = has_more @fetch_page = fetch_page freeze end |
Instance Attribute Details
#data ⇒ Array<Object> (readonly)
Items on this page — already deserialized into typed POROs by the resource's fetch_page.
42 43 44 |
# File 'lib/dinie/runtime/paginator.rb', line 42 def data @data end |
#has_more ⇒ Boolean (readonly)
Whether the API reports more pages after this one — the ONLY end-of-list signal.
45 46 47 |
# File 'lib/dinie/runtime/paginator.rb', line 45 def has_more @has_more end |
Class Method Details
.build(envelope, fetch_page) ⇒ void
This method returns an undefined value.
Wrap a fetched { data:, has_more: } envelope in a Page.
67 68 69 |
# File 'lib/dinie/runtime/paginator.rb', line 67 def build(envelope, fetch_page) new(data: envelope[:data] || [], has_more: envelope[:has_more] == true, fetch_page: fetch_page) end |
.from_fetch(fetch_page) ⇒ void
This method returns an undefined value.
Build the FIRST page by invoking fetch_page with no cursor.
57 58 59 |
# File 'lib/dinie/runtime/paginator.rb', line 57 def from_fetch(fetch_page) build(fetch_page.call(nil), fetch_page) end |
.paginated?(envelope) ⇒ Boolean
A list endpoint is paginated iff its envelope carries has_more.
77 78 79 |
# File 'lib/dinie/runtime/paginator.rb', line 77 def paginated?(envelope) envelope.is_a?(Hash) && envelope.key?(:has_more) end |
Instance Method Details
#each ⇒ self #each ⇒ Enumerator[Elem, self] Also known as: auto_paging_each
Auto-paginate: yield every item of this page, then every following page. No block → Enumerator.
117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/dinie/runtime/paginator.rb', line 117 def each(&block) return enum_for(:each) unless block_given? page = self loop do page.data.each(&block) break unless page.next_page? page = page.next_page end self end |
#each_page ⇒ self #each_page ⇒ Enumerator[Page[Elem], self]
Yield page-by-page (manual control). No block → Enumerator over the pages.
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/dinie/runtime/paginator.rb', line 136 def each_page return enum_for(:each_page) unless block_given? page = self loop do yield page break unless page.next_page? page = page.next_page end self end |
#first(count = UNSPECIFIED) ⇒ Object
The first count items across pages (or the very first item), fetched lazily. (count
defaults to a private sentinel, so the signature is loose; returns Elem? or Array[Elem].)
154 155 156 157 158 |
# File 'lib/dinie/runtime/paginator.rb', line 154 def first(count = UNSPECIFIED) return auto_paging_each.first if count.equal?(UNSPECIFIED) auto_paging_each.first(count) end |
#next_page ⇒ Page[Elem]
Fetch the next page (cursor = id of this page's last item). Guard with next_page?.
106 107 108 109 110 |
# File 'lib/dinie/runtime/paginator.rb', line 106 def next_page raise Dinie::Error, NO_NEXT_PAGE_MESSAGE unless next_page? self.class.build(@fetch_page.call(@data.last.id), @fetch_page) end |
#next_page? ⇒ Boolean
Whether a next page can be fetched.
97 98 99 |
# File 'lib/dinie/runtime/paginator.rb', line 97 def next_page? @has_more && !@data.empty? end |