class OnlineMigrations::BackgroundMigrations::ResetCounters

@private

Attributes

counters[R]
model[R]
touch[R]

Public Class Methods

new(model_name, counters, options = {}) click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 9
def initialize(model_name, counters, options = {})
  @model = Object.const_get(model_name, false)
  @counters = counters
  @touch = options[:touch]
end

Public Instance Methods

count() click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 51
def count
  # Exact counts are expensive on large tables, since PostgreSQL
  # needs to do a full scan. An estimated count should give a pretty decent
  # approximation of rows count in this case.
  Utils.estimated_count(connection, model.table_name)
end
process_batch(relation) click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 19
      def process_batch(relation)
        updates = counters.map do |counter_association|
          has_many_association = has_many_association(counter_association)

          foreign_key  = has_many_association.foreign_key.to_s
          child_class  = has_many_association.klass
          reflection   = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
          counter_name = reflection.counter_cache_column

          quoted_association_table = connection.quote_table_name(has_many_association.table_name)
          count_subquery = <<-SQL.strip_heredoc
            SELECT COUNT(*)
            FROM #{quoted_association_table}
            WHERE #{quoted_association_table}.#{connection.quote_column_name(foreign_key)} =
              #{model.quoted_table_name}.#{model.quoted_primary_key}
          SQL

          "#{connection.quote_column_name(counter_name)} = (#{count_subquery})"
        end

        if touch
          names = touch if touch != true
          names = Array.wrap(names)
          options = names.extract_options!
          touch_updates = touch_attributes_with_time(*names, **options)
          # In ActiveRecord 4.2 sanitize_sql_for_assignment is protected
          updates << model.send(:sanitize_sql_for_assignment, touch_updates)
        end

        relation.update_all(updates.join(", "))
      end
relation() click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 15
def relation
  model.unscoped
end

Private Instance Methods

connection() click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 96
def connection
  model.connection
end
has_many_association(counter_association) click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 59
def has_many_association(counter_association) # rubocop:disable Naming/PredicateName
  has_many_association = model.reflect_on_association(counter_association)

  unless has_many_association
    has_many = model.reflect_on_all_associations(:has_many)

    has_many_association = has_many.find do |association|
      counter_cache_column = association.counter_cache_column

      # ActiveRecord <= 4.2 is able to return only explicitly provided `counter_cache` column.
      if !counter_cache_column && Utils.ar_version <= 4.2
        counter_cache_column = "#{association.name}_count"
      end
      counter_cache_column && counter_cache_column.to_sym == counter_association.to_sym
    end

    counter_association = has_many_association.plural_name if has_many_association
  end
  raise ArgumentError, "'#{model.name}' has no association called '#{counter_association}'" unless has_many_association

  if has_many_association.is_a?(ActiveRecord::Reflection::ThroughReflection)
    has_many_association = has_many_association.through_reflection
  end

  has_many_association
end
timestamp_attributes_for_update() click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 92
def timestamp_attributes_for_update
  ["updated_at", "updated_on"].map { |name| model.attribute_aliases[name] || name }
end
touch_attributes_with_time(*names, time: nil) click to toggle source
# File lib/online_migrations/background_migrations/reset_counters.rb, line 86
def touch_attributes_with_time(*names, time: nil)
  attribute_names = timestamp_attributes_for_update & model.column_names
  attribute_names |= names.map(&:to_s)
  attribute_names.map { |attribute_name| [attribute_name, time || Time.current] }.to_h
end