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

Returns a new instance of Reconciliation.



63
64
65
66
67
# File 'lib/counter_culture/reconciler.rb', line 63

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.



58
59
60
# File 'lib/counter_culture/reconciler.rb', line 58

def counter
  @counter
end

#optionsObject (readonly)

Returns the value of attribute options.



58
59
60
# File 'lib/counter_culture/reconciler.rb', line 58

def options
  @options
end

#relation_classObject (readonly)

Returns the value of attribute relation_class.



58
59
60
# File 'lib/counter_culture/reconciler.rb', line 58

def relation_class
  @relation_class
end

Instance Method Details

#performObject

Raises:

  • (ArgumentError)


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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/counter_culture/reconciler.rb', line 69

def perform
  log "Performing reconciling of #{counter.model}##{counter.relation.to_sentence}."
  # 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 =
    case column_names
    when Proc
      if column_names.lambda? && column_names.arity == 0
        column_names.call
      else
        column_names.call(options.fetch(:context, {}))
      end
    when Hash
      column_names
    else
      { nil => counter_cache_name }
    end

  raise ArgumentError, ":column_names must be a Hash of conditions and column names" unless counter_column_names.is_a?(Hash)

  if options[:column_name]
    counter_column_names = counter_column_names.select do |_, v|
      options[:column_name].to_s == v.to_s
    end
  end

  # 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

    relation_class_table_name = quote_table_name(relation_class.table_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, " \
      "MAX(#{relation_class_table_name}.#{column_name}) AS #{column_name}"
    )

    # we need to join together tables until we get back to the table this class itself lives in
    join_clauses(where).each do |join|
      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 = counts_query.where(options[:where]).group(full_primary_key(relation_class))

    find_in_batches_args = { batch_size: batch_size }
    find_in_batches_args[:start] = options[:start] if options[:start].present?
    find_in_batches_args[:finish] = options[:finish] if options[:finish].present?

    with_reading_db_connection do
      counts_query.find_in_batches(**find_in_batches_args).with_index(1) do |records, index|
        log "Processing batch ##{index}."
        # now iterate over all the models and see whether their counts are right
        update_count_for_batch(column_name, records)
        log "Finished batch ##{index}."
      end
    end
  end
  log_without_newline "\n"
  log "Finished reconciling of #{counter.model}##{counter.relation.to_sentence}."
end