class JobIteration::ActiveRecordCursor

Curious about how this works from the SQL perspective? Check “Pagination Done the Right way”: bit.ly/2Rq7iPF

Attributes

position[R]
reached_end[RW]

Public Class Methods

new(relation, columns = nil, position = nil) click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 21
def initialize(relation, columns = nil, position = nil)
  columns ||= "#{relation.table_name}.#{relation.primary_key}"
  @columns = Array.wrap(columns)
  self.position = Array.wrap(position)
  raise ArgumentError, "Must specify at least one column" if columns.empty?
  if relation.joins_values.present? && !@columns.all? { |column| column.to_s.include?(".") }
    raise ArgumentError, "You need to specify fully-qualified columns if you join a table"
  end

  if relation.arel.orders.present? || relation.arel.taken.present?
    raise ConditionNotSupportedError
  end

  @base_relation = relation.reorder(@columns.join(","))
  @reached_end = false
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 38
def <=>(other)
  if reached_end != other.reached_end
    reached_end ? 1 : -1
  else
    position <=> other.position
  end
end
next_batch(batch_size) click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 58
def next_batch(batch_size)
  return nil if @reached_end

  relation = @base_relation.limit(batch_size)

  if (conditions = self.conditions).any?
    relation = relation.where(*conditions)
  end

  records = relation.uncached do
    relation.to_a
  end

  update_from_record(records.last) unless records.empty?
  @reached_end = records.size < batch_size

  records.empty? ? nil : records
end
position=(position) click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 46
def position=(position)
  raise "Cursor position cannot contain nil values" if position.any?(&:nil?)
  @position = position
end
update_from_record(record) click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 51
def update_from_record(record)
  self.position = @columns.map do |column|
    method = column.to_s.split(".").last
    record.send(method.to_sym)
  end
end

Protected Instance Methods

conditions() click to toggle source
# File lib/job-iteration/active_record_cursor.rb, line 79
def conditions
  i = @position.size - 1
  column = @columns[i]
  conditions = if @columns.size == @position.size
    "#{column} > ?"
  else
    "#{column} >= ?"
  end
  while i > 0
    i -= 1
    column = @columns[i]
    conditions = "#{column} > ? OR (#{column} = ? AND (#{conditions}))"
  end
  ret = @position.reduce([conditions]) { |params, value| params << value << value }
  ret.pop
  ret
end