| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is a 4th attempt to make counter cache transactional completely.
Past attempts: #9236, #14849, #23357.
All existing counter cache issues (increment/decrement twice, lost
increment) are caused due to updating counter cache on the outside of
the record saving transaction by assigning belongs_to record, even
though assigning that doesn't cause the record saving.
We have the `@_after_replace_counter_called` guard condition to mitigate
double increment/decrement issues, but we can't completely prevent that
inconsistency as long as updating counter cache on the outside of the
transaction, since saving the record is not always happened after that.
We already have handling counter cache after create/update/destroy,
https://github.com/rails/rails/blob/1b90f614b1b3d06b7f02a8b9ea6cd84f15d58643/activerecord/lib/active_record/counter_cache.rb#L162-L189
https://github.com/rails/rails/blob/1b90f614b1b3d06b7f02a8b9ea6cd84f15d58643/activerecord/lib/active_record/associations/builder/belongs_to.rb#L33-L59
so just removing assigning logic on the belongs_to association makes
counter cache transactional completely.
Closes #14849.
Closes #23357.
Closes #31493.
Closes #31494.
Closes #32372.
Closes #33113.
Closes #33117
Closes #33129.
Closes #33458.
|
|
|
|
| |
To prevent style check in review like https://github.com/rails/rails/pull/33608#discussion_r211087605.
|
|
|
|
|
|
| |
The target object for counter cache is not always determined by the
primary key value on the model. I'd like to extract `update_couters`
onto the `Relation` for the internal use.
|
|
|
|
|
|
|
|
|
|
|
| |
On belongs_to with `touch: true` association, unassigned object is
caused touching, but assigned object is not touched.
And also, if primary key is customized, it will touch against the wrong
target looked up by the customized key as primary key.
This change ensures correctly touching consistently between assigning
and unassigning.
|
| |
|
|
|
|
| |
Since UPDATE with a subquery doesn't work on MySQL.
|
|
|
|
|
|
|
|
|
|
| |
counter
If belongs_to primary key is customized, the callback will update
counters against the wrong target looked up by the customized key as
primary key.
We need to convert the customized key into an object that can be
referred to as primary key.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
```
# 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.
|
|\
| |
| | |
Fix `counter_cache` double increment
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
When an `after_create` callback did `update_attributes` on a record with
multiple `belongs_to` associations with counter caches, even numbered
associations would have their counters double-incremented. Fixes to
`ActiveModel::Dirty` in 020abad fixed this.
This adds regression tests for this bug fixed incidentally in the other
commit, which also removed the need for the workaround using
@_after_create_counter_called.
|
|/ |
|
|
|
|
|
| |
This reverts commit 3420a14590c0e6915d8b6c242887f74adb4120f9, reversing
changes made to afb66a5a598ce4ac74ad84b125a5abf046dcf5aa.
|
| |
|
|
|
|
| |
Follow up of #28453.
|
|
|
|
|
|
|
|
|
|
|
| |
Use it to specify that an association should be initialized with a
particular record before validation. For example:
# Before
belongs_to :account
before_validation -> { self.account ||= Current.account }
# After
belongs_to :account, default: -> { Current.account }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
|
| |
|
|
|
|
|
| |
`:foreign_type` is a valid option for `belongs_to` and `has_one`
so remove this to `SingularAssociation`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The problem was that when saving an object, we would
call touch_later on the parent which wont be saved immediteally, and
it wont call any callbacks. That was working one level up because
we were calling touch, during the touch_later commit phase. However that still
didnt solve the problem when you have a 3+ levels of parents to be touched,
as calling touch would affect the parent, but it would be too late to run callbacks
on its grand-parent.
The solution for this, is instead, call touch_later upwards when the first
touch_later is called. So we make sure all the timestamps are updated without relying
on callbacks.
This also removed the hard dependency BelongsTo builder had with the TouchLater module.
So we can still have the old behaviour if TouchLater module is not included.
[fixes 5f5e6d924973003c105feb711cefdb726f312768]
[related #19324]
|
|
|
| |
There is no need to to assign reflection name to a variable, because it's called once.
|
|
|
|
|
|
| |
This class is only used internally. We should keep it out of public
documentation. This patch adds nodoc for
`ActiveRecord::Associations::Builder` and everything nested within.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Also removes a false positive test that depends on the fixed bug:
At this time, counter_cache does not work with polymorphic relationships
(which is a bug). The test was added to make sure that no
StaleObjectError is raised when the car is destroyed. No such error is
currently raised because the lock version is not incremented by
appending a wheel to the car.
Furthermore, `assert_difference` succeeds because `car.wheels.count`
does not check the counter cache, but the collection size. The test will
fail if it is replaced with `car.wheels_count || 0`.
|
|
|
|
|
|
|
|
|
|
| |
[fixes #18606]
Make belongs_to use touch over touch_later when running the callbacks.
Add more tests and small method rename
Thanks Jeremy for the feedback.
|
|
|
|
| |
Deprecate `required` option in favor of `optional` for belongs_to.
|
| |
|
|\
| |
| |
| |
| |
| |
| |
| |
| | |
Fix bug, when ':dependent => :destroy' violates foreign key constraints
Conflicts:
activerecord/CHANGELOG.md
activerecord/lib/active_record/associations/builder/association.rb
activerecord/lib/active_record/associations/builder/has_one.rb
|
| |
| |
| |
| | |
constraints, issue #12380
|
| | |
|
| | |
|
| |
| |
| |
| | |
destroy
|
| |
| |
| |
| | |
create
|
| | |
|
| |
| |
| |
| | |
Fixes: #13445
|
| |
| |
| |
| | |
This is to get activerecord-deprecated_finders work again
|
| |
| |
| |
| | |
It is need in activerecord-deprecated_finders
|
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Example: Given you have a comments model with a polymorphic commentable
association (e.g. books and songs) with the touch option set.
Every time you update a comment its commentable should be touched.
This was working when you changed attributes on the comment or when you
moved the comment from one book to another. However, it was not working
when moving a comment from a book to a song. This is now fixed.
|
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Previously, the `has_one` macro incorrectly accepts the `counter_cache` option
due to a bug, although that options was never supported nor functional on
`has_one` and `has_one ... through` relationships. It now correctly raises an
`ArgumentError` when passed that option.
For reference, this bug was introduced in 52f8e4b9.
|
| | |
|
| | |
|
| | |
|
| |
| |
| |
| | |
the instance
|
| | |
|
| | |
|
| | |
|
| |
| |
| |
| | |
All the information is on the reflection, so just get it there.
|
| | |
|
| | |
|
| | |
|
|/ |
|
| |
|