1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Update other counter caches on destroy

This commit is contained in:
Ian Young 2012-09-17 14:57:13 -07:00
parent 34c7e73c1d
commit 66679c8ecd
11 changed files with 45 additions and 9 deletions

View file

@ -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

View file

@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder
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

View file

@ -22,7 +22,7 @@ module ActiveRecord
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

View file

@ -212,6 +212,7 @@ module ActiveRecord
# 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
@ -231,6 +232,19 @@ module ActiveRecord
@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?

View file

@ -427,6 +427,7 @@ module ActiveRecord
@readonly = false
@destroyed = false
@marked_for_destruction = false
@destroyed_by_association = nil
@new_record = true
@txn = nil
@_start_transaction_state = {}

View file

@ -115,6 +115,14 @@ class CounterCacheTest < ActiveRecord::TestCase
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

View file

@ -2,3 +2,6 @@ david:
id: 1
bred_dogs_count: 0
trained_dogs_count: 1
joanna:
id: 2
dogs_count: 1

View file

@ -1,3 +1,4 @@
sophie:
id: 1
trainer_id: 1
dog_lover_id: 2

View file

@ -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

View file

@ -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

View file

@ -230,14 +230,16 @@ ActiveRecord::Schema.define do
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|