Skip to content

Commit

Permalink
Refactor counter cache to extract `decrement_counters_before_last_sav…
Browse files Browse the repository at this point in the history
…e` on the belongs_to association
  • Loading branch information
kamipo committed Sep 27, 2018
1 parent 32da8c1 commit 688c27c
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 48 deletions.
Expand Up @@ -34,14 +34,28 @@ def updated?
@updated
end

def decrement_counters # :nodoc:
def decrement_counters
update_counters(-1)
end

def increment_counters # :nodoc:
def increment_counters
update_counters(1)
end

def decrement_counters_before_last_save
if reflection.polymorphic?
model_was = owner.attribute_before_last_save(reflection.foreign_type).try(:constantize)
else
model_was = klass
end

foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key)

if foreign_key_was && model_was < ActiveRecord::Base
update_counters_via_scope(model_was, foreign_key_was, -1)
end
end

def target_changed?
owner.saved_change_to_attribute?(reflection.foreign_key)
end
Expand All @@ -64,11 +78,16 @@ def update_counters(by)
if target && !stale_target?
target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
counter_cache_target.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by)
end
end
end

def update_counters_via_scope(klass, foreign_key, by)
scope = klass.unscoped.where!(primary_key(klass) => foreign_key)
scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
end

def find_target?
!loaded? && foreign_key_present? && klass
end
Expand All @@ -78,11 +97,11 @@ def require_counter_update?
end

def replace_keys(record)
owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
end

def primary_key(record)
reflection.association_primary_key(record.class)
def primary_key(klass)
reflection.association_primary_key(klass)
end

def foreign_key_present?
Expand All @@ -96,11 +115,6 @@ def invertible_for?(record)
inverse && inverse.has_one?
end

def counter_cache_target
primary_key = reflection.association_primary_key(klass)
klass.unscoped.where!(primary_key => owner._read_attribute(reflection.foreign_key))
end

def stale_state
result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
result && result.to_s
Expand Down
43 changes: 6 additions & 37 deletions activerecord/lib/active_record/associations/builder/belongs_to.rb
Expand Up @@ -21,47 +21,16 @@ def self.define_callbacks(model, reflection)
add_default_callbacks(model, reflection) if reflection.options[:default]
end

def self.define_accessors(mixin, reflection)
super
add_counter_cache_methods mixin
end

def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_update

mixin.class_eval do
def belongs_to_counter_cache_after_update(reflection)
if association(reflection.name).target_changed?
if reflection.polymorphic?
model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
else
model_was = reflection.klass
end

foreign_key_was = attribute_before_last_save(reflection.foreign_key)
cache_column = reflection.counter_cache_column

association(reflection.name).increment_counters

if foreign_key_was && model_was < ActiveRecord::Base
counter_cache_target(reflection, model_was, foreign_key_was).update_counters(cache_column => -1)
end
end
end

private
def counter_cache_target(reflection, model, foreign_key)
primary_key = reflection.association_primary_key(model)
model.unscoped.where!(primary_key => foreign_key)
end
end
end

def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column

model.after_update lambda { |record|
record.belongs_to_counter_cache_after_update(reflection)
association = association(reflection.name)

if association.target_changed?
association.increment_counters
association.decrement_counters_before_last_save
end
}

klass = reflection.class_name.safe_constantize
Expand Down

0 comments on commit 688c27c

Please sign in to comment.