未验证 提交 5236ddaf 编写于 作者: R Rafael França 提交者: Rafael Mendonça França

Merge pull request #31405 from...

Merge pull request #31405 from bogdanvlviv/fix-conflicts-counter_cache-with-touch-by-optimistic_locking

Fix conflicts `counter_cache` with `touch: true` by optimistic locking.
上级 aa389731
* Fix conflicts `counter_cache` with `touch: true` by optimistic locking.
```
# create_table :posts do |t|
# t.integer :comments_count, default: 0
# t.integer :lock_version
# t.timestamps
# end
class Post < ApplicationRecord
end
# create_table :comments do |t|
# t.belongs_to :post
# end
class Comment < ApplicationRecord
belongs_to :post, touch: true, counter_cache: true
end
```
Before:
```
post = Post.create!
# => begin transaction
INSERT INTO "posts" ("created_at", "updated_at", "lock_version")
VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0)
commit transaction
comment = Comment.create!(post: post)
# => begin transaction
INSERT INTO "comments" ("post_id") VALUES (1)
UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1,
"lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1
UPDATE "posts" SET "updated_at" = '2017-12-11 21:27:11.398330',
"lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0
rollback transaction
# => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post.
Comment.take.destroy!
# => begin transaction
DELETE FROM "comments" WHERE "comments"."id" = 1
UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1,
"lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1
UPDATE "posts" SET "updated_at" = '2017-12-11 21:42:47.785901',
"lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0
rollback transaction
# => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post.
```
After:
```
post = Post.create!
# => begin transaction
INSERT INTO "posts" ("created_at", "updated_at", "lock_version")
VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0)
commit transaction
comment = Comment.create!(post: post)
# => begin transaction
INSERT INTO "comments" ("post_id") VALUES (1)
UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1,
"lock_version" = COALESCE("lock_version", 0) + 1,
"updated_at" = '2017-12-11 21:37:09.802642' WHERE "posts"."id" = 1
commit transaction
comment.destroy!
# => begin transaction
DELETE FROM "comments" WHERE "comments"."id" = 1
UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1,
"lock_version" = COALESCE("lock_version", 0) + 1,
"updated_at" = '2017-12-11 21:39:02.685520' WHERE "posts"."id" = 1
commit transaction
```
Fixes #31199.
*bogdanvlviv*
* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block
without being connected.
......
......@@ -47,9 +47,9 @@ def increment_counters # :nodoc:
def update_counters(by)
if require_counter_update? && foreign_key_present?
if target && !stale_target?
target.increment!(reflection.counter_cache_column, by)
target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
klass.update_counters(target_id, reflection.counter_cache_column => by)
klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
end
end
end
......
......@@ -114,9 +114,13 @@ def self.add_touch_callbacks(model, reflection)
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
}}
model.after_save callback.(:saved_changes), if: :saved_changes?
model.after_touch callback.(:changes_to_save)
model.after_destroy callback.(:changes_to_save)
unless reflection.counter_cache_column
model.after_create callback.(:saved_changes), if: :saved_changes?
model.after_destroy callback.(:changes_to_save)
end
model.after_update callback.(:saved_changes), if: :saved_changes?
model.after_touch callback.(:changes_to_save)
end
def self.add_default_callbacks(model, reflection)
......
......@@ -356,11 +356,43 @@ def test_update_without_attributes_does_not_only_update_lock_version
end
end
def test_counter_cache_with_touch_and_lock_version
car = Car.create!
assert_equal 0, car.wheels_count
assert_equal 0, car.lock_version
previously_car_updated_at = car.updated_at
travel(1.second) do
Wheel.create!(wheelable: car)
end
assert_equal 1, car.reload.wheels_count
assert_not_equal previously_car_updated_at, car.updated_at
assert_equal 1, car.lock_version
previously_car_updated_at = car.updated_at
car.wheels.first.update(size: 42)
assert_equal 1, car.reload.wheels_count
assert_not_equal previously_car_updated_at, car.updated_at
assert_equal 2, car.lock_version
previously_car_updated_at = car.updated_at
travel(1.second) do
car.wheels.first.destroy!
end
assert_equal 0, car.reload.wheels_count
assert_not_equal previously_car_updated_at, car.updated_at
assert_equal 3, car.lock_version
end
def test_polymorphic_destroy_with_dependencies_and_lock_version
car = Car.create!
assert_difference "car.wheels.count" do
car.wheels << Wheel.create!
car.wheels.create
end
assert_difference "car.wheels.count", -1 do
car.reload.destroy
......
class Wheel < ActiveRecord::Base
belongs_to :wheelable, polymorphic: true, counter_cache: true
belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: true
end
......@@ -121,7 +121,7 @@
create_table :cars, force: true do |t|
t.string :name
t.integer :engines_count
t.integer :wheels_count
t.integer :wheels_count, default: 0
t.column :lock_version, :integer, null: false, default: 0
t.timestamps null: false
end
......@@ -962,6 +962,7 @@
end
create_table :wheels, force: true do |t|
t.integer :size
t.references :wheelable, polymorphic: true
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册