lib/update_in_batches.rb
# frozen_string_literal: true
module UpdateInBatches
# rubocop:disable all
refine ActiveRecord::Relation do
def update_in_batches(updates = {}, of: 10000, **kwargs)
updates = updates.merge(kwargs) if updates.is_a?(Hash)
relation = self
relation = relation.reorder(batch_order).limit(of)
batch_relation = relation
updated_total = 0
loop do
# Optimization: we only need to know if there's > 1, so we `limit 1`
break if batch_relation.limit(1).empty?
# Optimization: Use offset and limit to get the last id in the batch
offset = batch_relation.offset(of - 1).limit(1).pluck(:id)[0]
# Perform the update and store the update count returned by Postgres
updated_count = batch_relation.update_all(updates)
updated_total += updated_count
# If it's less than we expect, we've hit the last page
break if updated_count < of
# Set up the next batch
batch_relation = relation.where(table[primary_key].gt(offset))
end
updated_total
end
private
def batch_order
Arel.sql("#{quoted_table_name}.#{quoted_primary_key} ASC")
end
end
end