| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
| |
Fixes #31762.
|
|
|
|
|
|
| |
When removing a record from a has many through association, the counter
cache was being updated even if the through record halted the callback
chain and prevented itself from being destroyed.
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is a complete fix to #30995.
Originally alias tracker will only track table aliases on
`Arel::Nodes::Join`, other args are ignored.
Since c5ab6e5, parent aliases hash will be passed then it caused the
regression #30995.
It is enough to pass list of `Arel::Nodes::Join` simply, not need to
pass garbage args which will be ignored.
|
|\
| |
| |
| | |
When deleting through records, take into account association conditions
|
| |
| |
| |
| |
| |
| |
| |
| | |
Fixes #18424.
When deleting through records, it didn't take into account the
conditions that may have been affecting join model table, but was
defined in association definition.
|
| |
| |
| |
| | |
Need reloading when through record has replaced.
|
|\ \
| | |
| | |
| | |
| | |
| | | |
zoltankiss/allow-nested-has-many-associations-on-unpersisted-parent-instances
fix nested `has many :through` associations on unpersisted parent instances
|
| | |
| | |
| | |
| | | |
Fixes: #16313
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
I removed redundant `Array.wrap(records)` since `Preloader` is nodoc
class and Active Record always pass `records` as an array to
`Preloader`.
But if users relies on that behavior, it is not worth dropping its
behavior.
Fixes #31661.
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
Currently deleting through records doesn't respect `source_type`. It
should not be ignored in that case.
Related #23209.
Fixes #24116.
|
| | | |
|
| | |
| | |
| | |
| | | |
Follow up of 09cac8c67afdc4b2a1c6ae07931ddc082629b277.
|
|\ \ \
| | | |
| | | |
| | | | |
Use `count(:all)` in HasManyAssociation#count_records
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Problem: Calling `count` on an association can cause invalid SQL queries
to be created where the `SELECT COUNT(a, b, c)` function receives
multiple columns. This will cause a `StatementInvalid` exception later
on.
Solution: Use `count(:all)`, which generates a `SELECT COUNT(*)...`
query independently of the association.
This also includes a test case that, before the fix, broke.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
`ThroughReflection`
This method was introduced in #26718, which is internally used only in
`CollectionAssociation`. There is no need to be in the reflection
classes.
|
| | | |
| | | |
| | | |
| | | | |
when model is added to collection association
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
```
# 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.
|
| | | | |
|
| | | |
| | | |
| | | |
| | | | |
`CollectionProxy`
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
They are only different by one line of code which doesn't deserve a
hierarchy of 7 classes.
Closes #31079.
[Ryuta Kamizono & Bogdan Gusiev]
|
| | | | |
|
| | | |
| | | |
| | | |
| | | | |
`through_reflection` and `source_reflection` are used only in the class.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
`associated_records_by_owner` had returned customizing result before
calling `associate_records_to_owner` for through association subclasses.
Since #22115, `associate_records_to_owner` is called in the method and
not returned owner and result pairs. Removing the method will reduce
method call and block call nesting.
|
| | | | |
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
This is partially fixed by e617fb57 when through association has already
loaded. Otherwise, second level through association should respect
`preload_scope`.
Fixes #30242.
Closes #30076.
[Ryuta Kamizono & CicholGricenchos]
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
loaded
If through association has already loaded, `source_type` is ignored to
loaded through records. The loaded records should be filtered by
`source_type` in that case.
Fixes #30904.
|
| | | |
| | | |
| | | |
| | | | |
Removed unnecessary semicolons
|
| | | | |
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Currently alias tracker only refer a table name, doesn't respect an
alias name. Should use `join.left.name` rather than
`join.left.table_name`.
|
| | | |
| | | |
| | | |
| | | |
| | | | |
Currently we have no test for alias tracking with string joins. I've add
test case for that to catch a future regression.
|
| | | |
| | | |
| | | |
| | | | |
This basically reverts 9d4f79d3d394edb74fa2192e5d9ad7b09ce50c6d
|
| | | |
| | | |
| | | |
| | | |
| | | | |
Passing `true` to force an association to reload its records from the
database was deprecated in 5.0 and removed in 5.1.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
parent relation's aliases
Building association scope in join dependency should respect the parent
relation's aliases to avoid using the same alias name more than once.
Fixes #30681.
|
| | | |
| | | |
| | | |
| | | |
| | | | |
This is preparation to respect parent relation's alias tracking for
fixing #30681.
|
| | | | |
|
| | | | |
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Benchmark Script:
```
require 'active_record'
require 'benchmark/ips'
ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL'))
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :name, :email
t.integer :topic_id
t.timestamps null: false
end
create_table :topics, force: true do |t|
t.string :title
t.timestamps null: false
end
end
attributes = {
name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
email: 'foobar@email.com'
}
class Topic < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :topic
end
100.times do
User.create!(attributes)
end
users = User.first(50)
Topic.create!(title: 'This is a topic', users: users)
Benchmark.ips do |x|
x.config(time: 10, warmup: 5)
x.report("preload") do
User.includes(:topic).all.to_a
end
end
```
Before:
```
Calculating -------------------------------------
preload 40.000 i/100ms
-------------------------------------------------
preload 407.962 (± 1.5%) i/s - 4.080k
```
After:
```
alculating -------------------------------------
preload 43.000 i/100ms
-------------------------------------------------
preload 427.567 (± 1.6%) i/s - 4.300k
```
|
| | | | |
|
| | | |
| | | |
| | | |
| | | | |
`reflection.join_primary_key`
|
| | | | |
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Since we have `Preloader#preload`, `Preloader::Association#preload` is a
little confusing. And also, since the `preload` method is an abstract
method, it is hard to read where `associated_records_by_owner` is
called. This refactors `Preloader::Association` to ease to read where
`associated_records_by_owner` is called.
|
| | | |
| | | |
| | | |
| | | | |
`options` is never assigned to `scope` as long as using splat hash.
|
| | | | |
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
given
Related 2b5f5cdd7c1d95716de6a206b6d09ccbb006dc17.
If `reflection.scope` isn't given, `reflection_scope` is always empty
scope. It is unnecessary to merge it.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Related 2b5f5cdd7c1d95716de6a206b6d09ccbb006dc17.
If `through_scope` is empty scope, it is unnecessary to merge it.
And also, comparing relations is a little expensive (will cause
`build_arel`). It is enough to use `empty_scope?` to determine whether
empty scope.
|
| | | |
| | | |
| | | |
| | | | |
`through_scope` is not empty scope if `options[:source_type]` is given.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
`ActiveRecord::Associations::Preloader::Association`.
```
require 'active_record'
require 'benchmark/ips'
ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL'))
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :name, :email
t.integer :topic_id
t.timestamps null: false
end
create_table :topics, force: true do |t|
t.string :title
t.timestamps null: false
end
end
attributes = {
name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
email: 'foobar@email.com'
}
class Topic < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :topic
end
100.times do
User.create!(attributes)
end
users = User.first(50)
100.times do
Topic.create!(title: 'This is a topic', users: users)
end
Benchmark.ips do |x|
x.config(time: 10, warmup: 5)
x.report("preload") do
User.includes(:topic).all.to_a
end
end
```
```
Calculating -------------------------------------
preload 25.000 i/100ms
-------------------------------------------------
preload 251.772 (± 1.2%) i/s - 2.525k
```
```
Calculating -------------------------------------
preload 26.000 i/100ms
-------------------------------------------------
preload 270.392 (± 1.1%) i/s - 2.704k
```
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
This method was moved from `JoinHelper` in 0fddc3c1, but it is only used
for `table.create_join` in the internal and `Nodes::InnerJoin` is
default join klass. So it is not needed to pass it explicitly.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Because `table` is part of `reflection`, don't need to pass it
explicitly. And also, naming `alias_name` to `table` is a little
confusing. `aliased_table` is preferable than `alias_name`.
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
The order of scope evaluation should be from through scope to the
association's own scope. Otherwise the association's scope cannot affect
to through scope.
Fixes #13677.
Closes #28449.
|