Class: Parse::Cursor
- Inherits:
-
Object
- Object
- Parse::Cursor
- Includes:
- Enumerable
- Defined in:
- lib/parse/query/cursor.rb
Overview
A cursor-based pagination iterator for efficiently traversing large datasets.
Unlike skip/offset pagination which becomes increasingly slow for large datasets, cursor-based pagination uses the last seen objectId to efficiently fetch the next page. This approach maintains consistent performance regardless of how deep into the dataset you paginate.
Constant Summary collapse
- MAX_PAGE_SIZE =
Maximum page size allowed (Parse Server limit)
1000- DEFAULT_PAGE_SIZE =
Default page size
100
Instance Attribute Summary collapse
-
#items_fetched ⇒ Integer
readonly
The total number of items fetched so far.
-
#order_direction ⇒ Symbol
readonly
The order direction (:asc or :desc).
-
#order_field ⇒ Symbol
readonly
The field to order by for cursor positioning.
-
#page_size ⇒ Integer
readonly
The number of items per page.
-
#pages_fetched ⇒ Integer
readonly
The number of pages fetched so far.
-
#position ⇒ String?
readonly
The current cursor position (objectId of last item).
-
#query ⇒ Parse::Query
readonly
The base query for this cursor.
Class Method Summary collapse
-
.deserialize(json_string) ⇒ Parse::Cursor
Deserialize a cursor from a previously serialized state.
-
.deserialize_value(value) ⇒ Object
Deserialize a value from JSON storage.
-
.from_json(json_string) ⇒ Parse::Cursor
Alias for deserialize.
Instance Method Summary collapse
-
#all ⇒ Array<Parse::Object>
Fetch all results at once.
-
#each {|Parse::Object| ... } ⇒ self
Iterate over each individual item.
-
#each_page {|Array<Parse::Object>| ... } ⇒ self
Iterate over each page of results.
-
#exhausted? ⇒ Boolean
Check if the cursor has been exhausted (no more results).
-
#initialize(query, limit: DEFAULT_PAGE_SIZE, order: nil) ⇒ Cursor
constructor
Create a new cursor-based paginator.
-
#more_pages? ⇒ Boolean
Check if more pages are available.
-
#next_page ⇒ Array<Parse::Object>, Array
Fetch the next page of results.
-
#reset! ⇒ self
Reset the cursor to the beginning.
-
#serialize ⇒ String
Serialize the cursor state to a JSON string for persistence.
-
#stats ⇒ Hash
Get current cursor statistics.
-
#to_json ⇒ String
Alias for serialize.
Constructor Details
#initialize(query, limit: DEFAULT_PAGE_SIZE, order: nil) ⇒ Cursor
Create a new cursor-based paginator.
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/parse/query/cursor.rb', line 71 def initialize(query, limit: DEFAULT_PAGE_SIZE, order: nil) @query = query.dup @page_size = validate_page_size(limit) @position = nil @pages_fetched = 0 @items_fetched = 0 @exhausted = false # Set up ordering - cursor pagination needs a stable order setup_ordering(order) end |
Instance Attribute Details
#items_fetched ⇒ Integer (readonly)
Returns the total number of items fetched so far.
55 56 57 |
# File 'lib/parse/query/cursor.rb', line 55 def items_fetched @items_fetched end |
#order_direction ⇒ Symbol (readonly)
Returns the order direction (:asc or :desc).
61 62 63 |
# File 'lib/parse/query/cursor.rb', line 61 def order_direction @order_direction end |
#order_field ⇒ Symbol (readonly)
Returns the field to order by for cursor positioning.
58 59 60 |
# File 'lib/parse/query/cursor.rb', line 58 def order_field @order_field end |
#page_size ⇒ Integer (readonly)
Returns the number of items per page.
46 47 48 |
# File 'lib/parse/query/cursor.rb', line 46 def page_size @page_size end |
#pages_fetched ⇒ Integer (readonly)
Returns the number of pages fetched so far.
52 53 54 |
# File 'lib/parse/query/cursor.rb', line 52 def pages_fetched @pages_fetched end |
#position ⇒ String? (readonly)
Returns the current cursor position (objectId of last item).
49 50 51 |
# File 'lib/parse/query/cursor.rb', line 49 def position @position end |
#query ⇒ Parse::Query (readonly)
Returns the base query for this cursor.
43 44 45 |
# File 'lib/parse/query/cursor.rb', line 43 def query @query end |
Class Method Details
.deserialize(json_string) ⇒ Parse::Cursor
Deserialize a cursor from a previously serialized state.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/parse/query/cursor.rb', line 249 def self.deserialize(json_string) require "json" state = JSON.parse(json_string, symbolize_names: true) # Validate required fields required = [:class_name, :page_size, :order_field, :order_direction] missing = required.select { |f| state[f].nil? } unless missing.empty? raise ArgumentError, "Invalid cursor state: missing #{missing.join(", ")}" end # Get the model class klass = Parse::Model.find_class(state[:class_name]) unless klass raise ArgumentError, "Unknown Parse class: #{state[:class_name]}" end # Rebuild the query query = klass.query(state[:constraints] || {}) # Create the cursor with the original order order = state[:order_direction].to_sym == :desc ? state[:order_field].to_s.to_sym.desc : state[:order_field].to_s.to_sym.asc cursor = new(query, limit: state[:page_size], order: order) # Restore state cursor.instance_variable_set(:@position, state[:position]) cursor.instance_variable_set(:@last_order_value, deserialize_value(state[:last_order_value])) cursor.instance_variable_set(:@last_object_id, state[:last_object_id]) cursor.instance_variable_set(:@pages_fetched, state[:pages_fetched] || 0) cursor.instance_variable_set(:@items_fetched, state[:items_fetched] || 0) cursor.instance_variable_set(:@exhausted, state[:exhausted] || false) cursor end |
.deserialize_value(value) ⇒ Object
Deserialize a value from JSON storage
309 310 311 312 |
# File 'lib/parse/query/cursor.rb', line 309 def self.deserialize_value(value) return value unless value.is_a?(Hash) && value["__type"] == "Date" DateTime.parse(value["iso"]) end |
.from_json(json_string) ⇒ Parse::Cursor
Alias for deserialize
290 291 292 |
# File 'lib/parse/query/cursor.rb', line 290 def self.from_json(json_string) deserialize(json_string) end |
Instance Method Details
#all ⇒ Array<Parse::Object>
Fetch all results at once. Use with caution on large datasets.
180 181 182 183 184 |
# File 'lib/parse/query/cursor.rb', line 180 def all results = [] each_page { |page| results.concat(page) } results end |
#each {|Parse::Object| ... } ⇒ self
Iterate over each individual item. This is provided for Enumerable compatibility.
167 168 169 170 171 172 173 174 175 |
# File 'lib/parse/query/cursor.rb', line 167 def each(&block) return enum_for(:each) unless block_given? each_page do |page| page.each(&block) end self end |
#each_page {|Array<Parse::Object>| ... } ⇒ self
Iterate over each page of results.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/parse/query/cursor.rb', line 151 def each_page return enum_for(:each_page) unless block_given? while more_pages? page = next_page break if page.empty? yield page end self end |
#exhausted? ⇒ Boolean
Check if the cursor has been exhausted (no more results).
108 109 110 |
# File 'lib/parse/query/cursor.rb', line 108 def exhausted? @exhausted end |
#more_pages? ⇒ Boolean
Check if more pages are available.
102 103 104 |
# File 'lib/parse/query/cursor.rb', line 102 def more_pages? !@exhausted end |
#next_page ⇒ Array<Parse::Object>, Array
Fetch the next page of results.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/parse/query/cursor.rb', line 115 def next_page return [] if @exhausted # Build the page query page_query = build_page_query # Execute the query results = page_query.results # Update state if results.empty? || results.size < @page_size @exhausted = true end unless results.empty? @pages_fetched += 1 @items_fetched += results.size @position = extract_cursor_position(results.last) end results end |
#reset! ⇒ self
Reset the cursor to the beginning.
140 141 142 143 144 145 146 |
# File 'lib/parse/query/cursor.rb', line 140 def reset! @position = nil @pages_fetched = 0 @items_fetched = 0 @exhausted = false self end |
#serialize ⇒ String
Serialize the cursor state to a JSON string for persistence. Useful for background jobs that may be interrupted and resumed.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/parse/query/cursor.rb', line 215 def serialize require "json" state = { class_name: @query.table, constraints: @query.constraints(true), page_size: @page_size, position: @position, last_order_value: serialize_value(@last_order_value), last_object_id: @last_object_id, pages_fetched: @pages_fetched, items_fetched: @items_fetched, exhausted: @exhausted, order_field: @order_field, order_direction: @order_direction, version: 1, # For future compatibility } JSON.generate(state) end |
#stats ⇒ Hash
Get current cursor statistics.
188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/parse/query/cursor.rb', line 188 def stats { pages_fetched: @pages_fetched, items_fetched: @items_fetched, page_size: @page_size, exhausted: @exhausted, position: @position, order_field: @order_field, order_direction: @order_direction, } end |
#to_json ⇒ String
Alias for serialize
236 237 238 |
# File 'lib/parse/query/cursor.rb', line 236 def to_json serialize end |