class BlindIndex::Backfill

Attributes

blind_indexes[R]

Public Class Methods

new(relation, batch_size:, columns:) click to toggle source
# File lib/blind_index/backfill.rb, line 5
def initialize(relation, batch_size:, columns:)
  @relation = relation
  @transaction = @relation.respond_to?(:transaction)
  @batch_size = batch_size
  @blind_indexes = @relation.blind_indexes
  filter_columns!(columns) if columns
end

Public Instance Methods

perform() click to toggle source
# File lib/blind_index/backfill.rb, line 13
def perform
  each_batch do |records|
    backfill_records(records)
  end
end

Private Instance Methods

backfill_records(records) click to toggle source
# File lib/blind_index/backfill.rb, line 83
def backfill_records(records)
  # do expensive blind index computation outside of transaction
  records.each do |record|
    blind_indexes.each do |k, v|
      record.send("compute_#{k}_bidx") if !record.send(v[:bidx_attribute])
    end
  end

  # don't need to save records that went from nil => nil
  records.select! { |r| r.changed? }

  if records.any?
    with_transaction do
      records.each do |record|
        record.save!(validate: false)
      end
    end
  end
end
build_relation() click to toggle source
# File lib/blind_index/backfill.rb, line 29
def build_relation
  # build relation
  relation = @relation

  if defined?(ActiveRecord::Base) && relation.is_a?(ActiveRecord::Base)
    relation = relation.unscoped
  end

  # convert from possible class to ActiveRecord::Relation or Mongoid::Criteria
  relation = relation.all

  attributes = blind_indexes.map { |_, v| v[:bidx_attribute] }

  if defined?(ActiveRecord::Relation) && relation.is_a?(ActiveRecord::Relation)
    base_relation = relation.unscoped
    or_relation = relation.unscoped

    attributes.each_with_index do |attribute, i|
      or_relation =
        if i == 0
          base_relation.where(attribute => nil)
        else
          or_relation.or(base_relation.where(attribute => nil))
        end
    end

    relation.merge(or_relation)
  else
    relation.merge(relation.unscoped.or(attributes.map { |a| {a => nil} }))
  end
end
each_batch() { |records| ... } click to toggle source
# File lib/blind_index/backfill.rb, line 61
def each_batch
  relation = build_relation

  if relation.respond_to?(:find_in_batches)
    relation.find_in_batches(batch_size: @batch_size) do |records|
      yield records
    end
  else
    # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
    # use cursor for Mongoid
    records = []
    relation.all.each do |record|
      records << record
      if records.length == @batch_size
        yield records
        records = []
      end
    end
    yield records if records.any?
  end
end
filter_columns!(columns) click to toggle source

modify in-place

# File lib/blind_index/backfill.rb, line 22
def filter_columns!(columns)
  columns = Array(columns).map(&:to_s)
  blind_indexes.select! { |_, v| columns.include?(v[:bidx_attribute]) }
  bad_columns = columns - blind_indexes.map { |_, v| v[:bidx_attribute] }
  raise ArgumentError, "Bad column: #{bad_columns.first}" if bad_columns.any?
end
with_transaction() { || ... } click to toggle source
# File lib/blind_index/backfill.rb, line 103
def with_transaction
  if @transaction
    @relation.transaction do
      yield
    end
  else
    yield
  end
end