Class: GenevaDrive::IterableStep
- Inherits:
-
Object
- Object
- GenevaDrive::IterableStep
- Defined in:
- lib/geneva_drive/iterable_step.rb
Overview
Object passed to resumable step blocks for cursor-based iteration.
Provides an API compatible with Rails 8.1's ActiveJob::Continuation::Step, plus GenevaDrive-specific extensions for iterating over records and batches.
Instance Attribute Summary collapse
-
#cursor ⇒ Object
readonly
Returns the value of attribute cursor.
-
#iterations ⇒ Integer
readonly
Returns the number of iterations completed during this execution (checkpoints via set!, advance! or checkpoint!).
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Instance Method Summary collapse
-
#advance! ⇒ void
Increments an integer cursor by 1 and checkpoints.
-
#advanced? ⇒ Boolean
Returns true if the cursor was changed during this execution.
-
#checkpoint! ⇒ void
Persists the current cursor, counts the iteration, and checks for interruption (max_iterations, max_runtime, shutdown, external pause/cancel).
-
#description ⇒ String
Returns a human-readable description of current position.
-
#initialize(name, cursor, execution:, resumed:, interrupter:) ⇒ IterableStep
constructor
Creates a new IterableStep for a resumable step execution.
-
#iterate_over(enumerable) {|Object| ... } ⇒ void
Iterates over an enumerable using index as cursor.
-
#iterate_over_records(relation, cursor: :id) {|ActiveRecord::Base| ... } ⇒ void
Iterates over an ActiveRecord relation using a column as cursor.
-
#iterate_over_subrelations(relation, batch_size:, cursor: :id) {|ActiveRecord::Relation| ... } ⇒ void
Iterates over an ActiveRecord relation in subrelations (batches).
-
#resumed? ⇒ Boolean
Returns true if this execution is resuming from a prior suspension.
-
#set!(value) ⇒ void
Sets the cursor to a new value and checkpoints.
-
#skip_to!(value, wait: nil) ⇒ void
Skips to a new cursor position and suspends execution.
-
#to_a ⇒ Array
Returns array representation [name, cursor] for compatibility.
Constructor Details
#initialize(name, cursor, execution:, resumed:, interrupter:) ⇒ IterableStep
Creates a new IterableStep for a resumable step execution.
33 34 35 36 37 38 39 40 41 |
# File 'lib/geneva_drive/iterable_step.rb', line 33 def initialize(name, cursor, execution:, resumed:, interrupter:) @name = name.to_sym @cursor = cursor @execution = execution @resumed = resumed @interrupter = interrupter @advanced = false @iterations = 0 end |
Instance Attribute Details
#cursor ⇒ Object (readonly)
Returns the value of attribute cursor.
24 25 26 |
# File 'lib/geneva_drive/iterable_step.rb', line 24 def cursor @cursor end |
#iterations ⇒ Integer (readonly)
Returns the number of iterations completed during this execution (checkpoints via set!, advance! or checkpoint!).
47 48 49 |
# File 'lib/geneva_drive/iterable_step.rb', line 47 def iterations @iterations end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
24 25 26 |
# File 'lib/geneva_drive/iterable_step.rb', line 24 def name @name end |
Instance Method Details
#advance! ⇒ void
This method returns an undefined value.
Increments an integer cursor by 1 and checkpoints. Only works with integer cursors.
81 82 83 84 |
# File 'lib/geneva_drive/iterable_step.rb', line 81 def advance! raise ArgumentError, "advance! requires integer cursor, got #{@cursor.inspect}" unless @cursor.is_a?(Integer) set!(@cursor + 1) end |
#advanced? ⇒ Boolean
Returns true if the cursor was changed during this execution.
61 62 63 |
# File 'lib/geneva_drive/iterable_step.rb', line 61 def advanced? @advanced end |
#checkpoint! ⇒ void
This method returns an undefined value.
Persists the current cursor, counts the iteration, and checks for interruption (max_iterations, max_runtime, shutdown, external pause/cancel). Called automatically by set! and advance!.
91 92 93 94 95 |
# File 'lib/geneva_drive/iterable_step.rb', line 91 def checkpoint! @iterations += 1 persist_cursor! check_interruption! end |
#description ⇒ String
Returns a human-readable description of current position.
107 108 109 |
# File 'lib/geneva_drive/iterable_step.rb', line 107 def description "at '#{name}', cursor '#{cursor.inspect}'" end |
#iterate_over(enumerable) {|Object| ... } ⇒ void
This method returns an undefined value.
Iterates over an enumerable using index as cursor. Resumes from the stored cursor position on subsequent runs.
132 133 134 135 136 137 138 139 140 141 |
# File 'lib/geneva_drive/iterable_step.rb', line 132 def iterate_over(enumerable) raise ArgumentError, "Must respond to #each" unless enumerable.respond_to?(:each) start_index = @cursor || 0 enumerable.each_with_index do |element, index| next if index < start_index yield element set!(index + 1) end end |
#iterate_over_records(relation, cursor: :id) {|ActiveRecord::Base| ... } ⇒ void
This method returns an undefined value.
Iterates over an ActiveRecord relation using a column as cursor. Uses find_each for memory-efficient batching.
150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/geneva_drive/iterable_step.rb', line 150 def iterate_over_records(relation, cursor: :id) filtered = if @cursor relation.where(relation.arel_table[cursor].gt(@cursor)).order(cursor => :asc) else relation.order(cursor => :asc) end filtered.find_each do |record| yield record set!(record.public_send(cursor)) end end |
#iterate_over_subrelations(relation, batch_size:, cursor: :id) {|ActiveRecord::Relation| ... } ⇒ void
This method returns an undefined value.
Iterates over an ActiveRecord relation in subrelations (batches). Each subrelation is yielded as an ActiveRecord relation with offset applied, useful for bulk operations like updates or exports.
172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/geneva_drive/iterable_step.rb', line 172 def iterate_over_subrelations(relation, batch_size:, cursor: :id) filtered = if @cursor relation.where(relation.arel_table[cursor].gt(@cursor)).order(cursor => :asc) else relation.order(cursor => :asc) end filtered.in_batches(of: batch_size) do |relation_with_offset| yield relation_with_offset last_cursor = relation_with_offset.maximum(cursor) set!(last_cursor) if last_cursor end end |
#resumed? ⇒ Boolean
Returns true if this execution is resuming from a prior suspension.
54 55 56 |
# File 'lib/geneva_drive/iterable_step.rb', line 54 def resumed? @resumed end |
#set!(value) ⇒ void
This method returns an undefined value.
Sets the cursor to a new value and checkpoints. The cursor can be any value that ActiveJob can serialize.
70 71 72 73 74 |
# File 'lib/geneva_drive/iterable_step.rb', line 70 def set!(value) @cursor = value @advanced = true checkpoint! end |
#skip_to!(value, wait: nil) ⇒ void
This method returns an undefined value.
Skips to a new cursor position and suspends execution. The job will be re-enqueued to continue from the new cursor.
119 120 121 122 123 124 |
# File 'lib/geneva_drive/iterable_step.rb', line 119 def skip_to!(value, wait: nil) @cursor = value @advanced = true persist_cursor! throw :interrupt, wait end |
#to_a ⇒ Array
Returns array representation [name, cursor] for compatibility.
100 101 102 |
# File 'lib/geneva_drive/iterable_step.rb', line 100 def to_a [name.to_s, cursor] end |