Skip to content

Commit

Permalink
Update other counter caches on destroy
Browse files Browse the repository at this point in the history
  • Loading branch information
iangreenleaf committed Mar 20, 2013
1 parent 34c7e73 commit 66679c8
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 9 deletions.
5 changes: 5 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##

* Models with multiple counter cache associations now update correctly on destroy.
See #7706.

*Ian Young*

* If inverse_of is true on an association, then when one calls +find()+ on
the association, ActiveRecord will first look through the in-memory objects
in the association for a particular id. Then, it will go to the DB if it
Expand Down
Expand Up @@ -31,7 +31,7 @@ def belongs_to_counter_cache_after_create_for_#{name}
end
def belongs_to_counter_cache_before_destroy_for_#{name}
unless marked_for_destruction?
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect}
record = #{name}
record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
end
Expand Down
Expand Up @@ -22,7 +22,7 @@ def handle_dependency
else
if options[:dependent] == :destroy
# No point in executing the counter update since we're going to destroy the parent anyway
load_target.each(&:mark_for_destruction)
load_target.each { |t| t.destroyed_by_association = reflection }
destroy_all
else
delete_all
Expand Down
14 changes: 14 additions & 0 deletions activerecord/lib/active_record/autosave_association.rb
Expand Up @@ -212,6 +212,7 @@ def add_autosave_association_callbacks(reflection)
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
def reload(options = nil)
@marked_for_destruction = false
@destroyed_by_association = nil
super
end

Expand All @@ -231,6 +232,19 @@ def marked_for_destruction?
@marked_for_destruction
end

# Records the association that is being destroyed and destroying this
# record in the process.
def destroyed_by_association=(reflection)
@destroyed_by_association = reflection
end

# Returns the association for the parent being destroyed.
#
# Used to avoid updating the counter cache unnecessarily.
def destroyed_by_association
@destroyed_by_association
end

# Returns whether or not this record has been changed in any way (including whether
# any of its nested autosave associations are likewise changed)
def changed_for_autosave?
Expand Down
1 change: 1 addition & 0 deletions activerecord/lib/active_record/core.rb
Expand Up @@ -427,6 +427,7 @@ def init_internals
@readonly = false
@destroyed = false
@marked_for_destruction = false
@destroyed_by_association = nil
@new_record = true
@txn = nil
@_start_transaction_state = {}
Expand Down
8 changes: 8 additions & 0 deletions activerecord/test/cases/counter_cache_test.rb
Expand Up @@ -115,6 +115,14 @@ class ::SpecialReply < ::Reply
end
end

test "update other counters on parent destroy" do
david, joanna = dog_lovers(:david, :joanna)

assert_difference 'joanna.reload.dogs_count', -1 do
david.destroy
end
end

test "reset the right counter if two have the same foreign key" do
michael = people(:michael)
assert_nothing_raised(ActiveRecord::StatementInvalid) do
Expand Down
3 changes: 3 additions & 0 deletions activerecord/test/fixtures/dog_lovers.yml
Expand Up @@ -2,3 +2,6 @@ david:
id: 1
bred_dogs_count: 0
trained_dogs_count: 1
joanna:
id: 2
dogs_count: 1
1 change: 1 addition & 0 deletions activerecord/test/fixtures/dogs.yml
@@ -1,3 +1,4 @@
sophie:
id: 1
trainer_id: 1
dog_lover_id: 2
5 changes: 3 additions & 2 deletions activerecord/test/models/dog.rb
@@ -1,4 +1,5 @@
class Dog < ActiveRecord::Base
belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count
belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count
belongs_to :breeder, class_name: "DogLover", counter_cache: :bred_dogs_count
belongs_to :trainer, class_name: "DogLover", counter_cache: :trained_dogs_count
belongs_to :doglover, foreign_key: :dog_lover_id, class_name: "DogLover", counter_cache: true
end
5 changes: 3 additions & 2 deletions activerecord/test/models/dog_lover.rb
@@ -1,4 +1,5 @@
class DogLover < ActiveRecord::Base
has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id
has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id
has_many :trained_dogs, class_name: "Dog", foreign_key: :trainer_id, dependent: :destroy
has_many :bred_dogs, class_name: "Dog", foreign_key: :breeder_id
has_many :dogs
end
8 changes: 5 additions & 3 deletions activerecord/test/schema/schema.rb
Expand Up @@ -230,14 +230,16 @@ def create_table(*args, &block)
t.integer :access_level, :default => 1
end

create_table :dog_lovers, :force => true do |t|
t.integer :trained_dogs_count, :default => 0
t.integer :bred_dogs_count, :default => 0
create_table :dog_lovers, force: true do |t|
t.integer :trained_dogs_count, default: 0
t.integer :bred_dogs_count, default: 0
t.integer :dogs_count, default: 0
end

create_table :dogs, :force => true do |t|
t.integer :trainer_id
t.integer :breeder_id
t.integer :dog_lover_id
end

create_table :edges, :force => true, :id => false do |t|
Expand Down

0 comments on commit 66679c8

Please sign in to comment.