Class: CounterCulture::Reconciler::Reconciliation

Inherits:
Object
  • Object
show all
Defined in:
lib/counter_culture/reconciler.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(counter, changes_holder, options, relation_class) ⇒ Reconciliation



61
62
63
64
65
# File 'lib/counter_culture/reconciler.rb', line 61

def initialize(counter, changes_holder, options, relation_class)
  @counter, @options, = counter, options
  @relation_class = relation_class
  @changes_holder = changes_holder
end

Instance Attribute Details

#counterObject (readonly)

Returns the value of attribute counter.



56
57
58
# File 'lib/counter_culture/reconciler.rb', line 56

def counter
  @counter
end

#optionsObject (readonly)

Returns the value of attribute options.



56
57
58
# File 'lib/counter_culture/reconciler.rb', line 56

def options
  @options
end

#relation_classObject (readonly)

Returns the value of attribute relation_class.



56
57
58
# File 'lib/counter_culture/reconciler.rb', line 56

def relation_class
  @relation_class
end

Instance Method Details

#performObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/counter_culture/reconciler.rb', line 67

def perform
  # if we're provided a custom set of column names with conditions, use them; just use the
  # column name otherwise
  # which class does this relation ultimately point to? that's where we have to start

  scope = relation_class

  counter_column_names = column_names || {nil => counter_cache_name}

  # iterate over all the possible counter cache column names
  counter_column_names.each do |where, column_name|
   # if the column name is nil, that means those records don't affect
   # counts; we don't need to do anything in that case. but we allow
   # specifying that condition regardless to make the syntax less
   # confusing
   next unless column_name

    # select join column and count (from above) as well as cache column ('column_name') for later comparison
    counts_query = scope.select("#{relation_class.table_name}.#{relation_class.primary_key}, #{relation_class.table_name}.#{relation_reflect(relation).association_primary_key(relation_class)}, #{count_select} AS count, #{relation_class.table_name}.#{column_name}")

    # we need to join together tables until we get back to the table this class itself lives in
    # conditions must also be applied to the join on which we are counting
    join_clauses.each_with_index do |join, index|
      if index == join_clauses.size - 1
        if where
          join += " AND (#{model.send(:sanitize_sql_for_conditions, where)})"
        end
        # respect the deleted_at column if it exists
        if model.column_names.include?('deleted_at')
          join += " AND #{model.table_name}.deleted_at IS NULL"
        end
      end
      counts_query = counts_query.joins(join)
    end

    # iterate in batches; otherwise we might run out of memory when there's a lot of
    # instances and we try to load all their counts at once
    batch_size = options.fetch(:batch_size, CounterCulture.config.batch_size)

    counts_query.group(full_primary_key(relation_class)).find_in_batches(batch_size: batch_size) do |records|
      # now iterate over all the models and see whether their counts are right
      ActiveRecord::Base.transaction do
        records.each do |record|
          count = record.read_attribute('count') || 0
          next if record.read_attribute(column_name) == count

          track_change(record, column_name, count)

          # use update_all because it's faster and because a fixed counter-cache shouldn't update the timestamp
          relation_class.where(relation_class.primary_key => record.send(relation_class.primary_key)).update_all(column_name => count)
        end
      end
    end
  end
end