File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 910910
911911 * Aaron Patterson*
912912
913+ * Use database lock to ensure data integrity on counter caches.
914+
915+ * Brian Durand*
916+
913917Please check [ 3-2-stable] ( https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md ) for previous changes.
Original file line number Diff line number Diff line change @@ -31,7 +31,9 @@ def belongs_to_counter_cache_after_create_for_#{name}
3131 def belongs_to_counter_cache_before_destroy_for_#{ name }
3232 unless marked_for_destruction?
3333 record = #{ name }
34- record.class.decrement_counter(:#{ cache_column } , record.id) unless record.nil?
34+ if record && self.class.obtain_lock(id)
35+ record.class.decrement_counter(:#{ cache_column } , record.id)
36+ end
3537 end
3638 end
3739 CODE
Original file line number Diff line number Diff line change @@ -54,6 +54,17 @@ module Locking
5454 # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
5555 # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
5656 module Pessimistic
57+ extend ActiveSupport ::Concern
58+
59+ module ClassMethods
60+ # Obtain a row level lock on the row with the specified primary key value (if row locking is supported).
61+ # Returns +true+ if the row exists or +false+ if the row does not exist.
62+ def obtain_lock ( id , lock = true )
63+ sql = where ( primary_key => id ) . select ( primary_key ) . lock ( lock ) . to_sql
64+ !!connection . select_one ( sql )
65+ end
66+ end
67+
5768 # Obtain a row lock on this record. Reloads the record to obtain the requested
5869 # lock. Pass an SQL locking clause to append the end of the SELECT statement
5970 # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
Original file line number Diff line number Diff line change @@ -393,6 +393,20 @@ def test_custom_counter_cache
393393 assert_equal 17 , reply . replies . size
394394 end
395395
396+ def test_counter_cache_with_destroy
397+ topic = Topic . create! ( :title => "Zoom-zoom-zoom" )
398+ topic . replies . create! ( :title => "re: zoom" , :content => "speedy quick!" )
399+
400+ assert_equal 1 , topic . reload [ :replies_count ]
401+
402+ reply_1 = Reply . find ( topic . replies . first . id )
403+ reply_2 = Reply . find ( topic . replies . first . id )
404+ reply_1 . destroy
405+ reply_2 . destroy
406+
407+ assert_equal 0 , topic . reload [ :replies_count ]
408+ end
409+
396410 def test_association_assignment_sticks
397411 post = Post . first
398412
Original file line number Diff line number Diff line change @@ -408,6 +408,11 @@ def test_with_lock_rolls_back_transaction
408408 assert_equal old , person . reload . first_name
409409 end
410410
411+ def test_obtain_lock
412+ assert Person . obtain_lock ( 1 )
413+ assert !Person . obtain_lock ( 0 )
414+ end
415+
411416 if current_adapter? ( :PostgreSQLAdapter , :OracleAdapter )
412417 def test_no_locks_no_wait
413418 first , second = duel { Person . find 1 }
You can’t perform that action at this time.
0 commit comments