fixes#40018
Prior to this commit, the following test would fail. `book`'s author was
actually being returned as `author_b`. (The same would happen for
`unscope`.)
def test
author_a = Author.create!
author_b = Author.create!
Book.create(author: author_a)
book = author_b.books.or(author_a.books).first
assert_equal book.author, author_a
end
Co-authored-by: Adam Hess <adamhess1991@gmail.com>
Sometimes cascading association deletions can cause timeouts due to
an IO issue. Perhaps a model has associations that are destroyed on
deletion which in turn trigger other deletions and this can continue
down a complex tree. Along this tree you may also hit other IO
operations. Such deep deletions can lead to server timeouts while
awaiting completion and really the user may not notice all the
changes on their side immediately making them wait unnecesarially or
worse causing a timeout during the operation.
We now allow associations supporting the `dependent:` key to take `:destroy_async`,
which schedules a background job to destroy associations.
Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
Co-authored-by: Rafael Mendonça França <rafael@franca.dev>
Co-authored-by: Cory Gwin @gwincr11 <gwincr11@github.com>
If a table is joined multiple times, those tables are aliased other than
the first one.
It happens easily on self referential associations, and in that case
currently there is no way to work custom attribute (type casting) and
attribute alias resolution for aliased tables in `where` conditions.
To address the issue, it will allow `where` references association names
as table aliases. If association names are referenced in `where`, those
names are used for joined table alias names.
```ruby
class Comment < ActiveRecord::Base
enum label: [:default, :child]
has_many :children, class_name: "Comment", foreign_key: :parent_id
end
# ... FROM comments LEFT OUTER JOIN comments children ON ... WHERE children.label = 1
Comment.includes(:children).where("children.label": "child")
```
Fixes#39727.
In 7f938ca the test model `Man` was renamed to `Human`. Maybe this is a
good time to also change `horrible_human` and `dirty_human` to
`happy_human` and `confused_human`.
While this change is mostly cosmetic change, the phrase "dirty man" has
a negative meaning.
The adjectives "confused" and "puzzled" were chosen because they are
used for defining associations with errors.
If an inverse association isn't found we can suggest similar associations:
```
class Human < ActiveRecord::Base
has_one :dirty_face, class_name: "Face", inverse_of: :dirty_human
end
class Face < ActiveRecord::Base
belongs_to :human, inverse_of: :face
belongs_to :horrible_human, class_name: "Human", inverse_of: :horrible_face
end
Human.first.dirty_face
Could not find the inverse association for dirty_face (:dirty_human in Face)
Did you mean? human
horrible_human
....
```
Co-authored-by: Daniel Colson <danieljamescolson@gmail.com>
This is an alternative of #29722.
Before Rails 6.1, storing demodulized class name is supported only for
STI type by `store_full_sti_class` class attribute.
Now `store_full_class_name` class attribute can handle both STI and
polymorphic types.
Closes#29722.
See also #29601, #32048, #32148.
After renaming `Man` to `Human` the variable letter `m` in these tests
ends up being pretty confusing. Rather than rename it to `h`, this
commit replaces it with the full word `human`.
Since I was already renaming things, I also went ahead and replaced `f`
with `face`, `i` with `interest`, and `a` with `author`.
The commit replaces the `Man` model used in tests with a `Human` model. It
also replaces the existing `Human` model with a `SuperHuman` model
inheriting from `Human`.
While this may seem like a cosmetic change, I see it as more of an
inclusivity change. I think it makes sense for a number of reasons:
* Prior to this commit the `Human` model inherited from `Man`. At best
this makes no sense (it should be the other way around). At worst it
is offensive and harmful to the community.
* It doesn't seem inclusive to me to have exclusively male-gendered
examples in the codebase.
* There is no particular reason for these examples to be gendered.
* `man` is hard to grep for, since it also matches `many, manager,
manual, etc`
For the most part this is a simple search and replace. The one exception
to that is that I had to add the table name to the model so we could use
"humans" instead of "humen".
Follow up of #40000.
In #40000, `eager_load(:general_categorizations, :general_posts)` works,
but `eager_load(:general_posts, :general_categorizations)` doesn't work
yet.
This implements the deduplication for the case of reversed eager loading
order.
I had found the issue while working on fixing #33525.
That is if duplicated association has a scope which has `where` with
explicit table name condition (e.g. `where("categories.name": "General")`),
that condition in all duplicated associations will filter the first one
only, other all duplicated associations are not filtered, since
duplicated joins will be aliased except the first one (e.g.
`INNER JOIN "categories" "categories_categorizations"`).
```ruby
class Author < ActiveRecord::Base
has_many :general_categorizations, -> { joins(:category).where("categories.name": "General") }, class_name: "Categorization"
has_many :general_posts, through: :general_categorizations, source: :post
end
authors = Author.eager_load(:general_categorizations, :general_posts).to_a
```
Generated eager loading query:
```sql
SELECT "authors"."id" AS t0_r0, ... FROM "authors"
-- `has_many :general_categorizations, -> { joins(:category).where("categories.name": "General") }`
LEFT OUTER JOIN "categorizations" ON "categorizations"."author_id" = "authors"."id"
INNER JOIN "categories" ON "categories"."id" = "categorizations"."category_id" AND "categories"."name" = ?
-- `has_many :general_posts, through: :general_categorizations, source: :post`
---- duplicated `through: :general_categorizations` part
LEFT OUTER JOIN "categorizations" "general_categorizations_authors_join" ON "general_categorizations_authors_join"."author_id" = "authors"."id"
INNER JOIN "categories" "categories_categorizations" ON "categories_categorizations"."id" = "general_categorizations_authors_join"."category_id" AND "categories"."name" = ? -- <-- filtering `"categories"."name" = ?` won't work
---- `source: :post` part
LEFT OUTER JOIN "posts" ON "posts"."id" = "general_categorizations_authors_join"."post_id"
```
Originally eager loading with join scope didn't work before Rails 5.2
(#29413), and duplicated through association with join scope raised a
duplicated alias error before alias tracking is improved in 590b045.
But now it will potentially be got incorrect result instead of an error,
it is worse than an error.
To fix the issue, it makes eager loading to deduplicate / re-use
duplicated through association if possible, like as `preload`.
```sql
SELECT "authors"."id" AS t0_r0, ... FROM "authors"
-- `has_many :general_categorizations, -> { joins(:category).where("categories.name": "General") }`
LEFT OUTER JOIN "categorizations" ON "categorizations"."author_id" = "authors"."id"
INNER JOIN "categories" ON "categories"."id" = "categorizations"."category_id" AND "categories"."name" = ?
-- `has_many :general_posts, through: :general_categorizations, source: :post`
---- `through: :general_categorizations` part is deduplicated / re-used
LEFT OUTER JOIN "posts" ON "posts"."id" = "categorizations"."post_id"
```
Fixes#32819.
In Active Record internal, `arel_table` is not directly used but
`arel_attribute` is used, since `arel_table` doesn't normalize an
attribute name as a string, and doesn't resolve attribute aliases.
For the above reason, `arel_attribute` should be used rather than
`arel_table`, but most people directly use `arel_table`, both
`arel_table` and `arel_attribute` are private API though.
Although I'd not recommend using private API, `arel_table` is actually
widely used, and it is also problematic for unscopeable queries and
hash-like relation merging friendly, as I explained at #39863.
To resolve the issue, this change moves Arel attribute normalization
(attribute name as a string, and attribute alias resolution) into
`arel_table`.
#39378 has changed to use `build_scope` in `join_scopes`, which rely on
`reflection.klass`, but `reflection.klass` is restricted for polymorphic
association, the klass for the association should be passed explicitly.
If source/through scope references other tables in where/order, we
should explicitly maintain joins in the scope, otherwise association
queries will fail with referenced unknown column.
Fixes#33525.
`reflection.scope` is not aware of all source scopes if the association
is through association.
It should use `reflection.join_scopes` for that.
Fixes#39376.
If a has_many :through association isn't found we can suggest similar associations:
```
class Author
has_many :categorizations, -> { }
has_many :categories, through: :categorizations
has_many :categorized_posts, through: :categorizations, source: :post
has_many :category_post_comments, through: :categories, source: :post_comments
has_many :nothings, through: :kateggorisatons, class_name: "Category"
end
Author.first.nothings
Could not find the association :kateggorisatons in model Author
Did you mean? categorizations
categories
categorized_posts
category_post_comments
```
If an association isn't found we can suggest matching associations:
```
Post.all.merge!(includes: :monkeys).find(6)
Association named 'monkeys' was not found on Post; perhaps you misspelled it?
Did you mean? funky_tags
comments
images
skimmers
```
#38354 is caused by #36304, to fix invalid joins order for through
associations.
Actually passing Arel joins to `joins` is not officially supported
unlike string joins, and also Arel joins could be easily converted to
string joins by `to_sql`. But I'd not intend to break existing apps
without deprecation cycle, so I've changed to mark only implicit joins
as leading joins, to maintain the original joins order for user supplied
Arel joins.
Fixes#38354.
#38597 is caused by #35864.
To reproduce this issue, at least it is required four different models
and three left joins from different relations.
When merging a relation from different model, new stashed (left) joins
should be placed before existing stashed joins, but #35864 had broken
that expectation if left joins are stashed multiple times.
This fixes that stashed left joins order as expected.
Fixes#38597.
That issues are caused by using only the model's cast types on the
relation.
To fix that issues, use the attribute's type caster takes precedence
over the model's cast types on the relation.
Fixes#35232.
Fixes#36042.
Fixes#37484.
Example failure: https://buildkite.com/rails/rails/builds/68661#84f8790a-fc9e-42ef-a7fb-5bd15a489de8/1002-1012
The failing `destroyed_by_association` tests create an author (a
DestroyByParentAuthor) and a book (a DestroyByParentBook) that belongs
to that author. If the database already contains books that refer to
that author's ID from previous tests (i.e. tests that disabled
`use_transactional_tests`), then one of those books will be loaded and
destroyed instead of the intended DestroyByParentBook book.
By loading the `:books` fixtures, we ensure the database does not
contain such unexpected books.
Co-authored-by: Eugene Kenny <elkenny@gmail.com>
Co-authored-by: Ryuta Kamizono <kamipo@gmail.com>
`in_clause_length` was added at c5a284f to address to Oracle IN clause
length limitation.
Now `in_clause_length` is entirely integrated in Arel visitor since
#35838 and #36074.
Since Oracle visitors are the only code that rely on `in_clause_length`.
so I'd like to remove that from Rails code base, like has removed Oracle
visitors (#38946).
Previously, if `build_association` was called multiple times for a `has_one` association but never committed to the database, the first newly-associated record would trigger `touch` during the attempted removal of the record.
For example:
class Post < ActiveRecord::Base
has_one :comment, inverse_of: :post, dependent: :destroy
end
class Comment < ActiveRecord::Base
belongs_to :post, inverse_of: :comment, touch: true
end
post = Post.create!
comment_1 = post.build_comment
comment_2 = post.build_comment
When `comment_2` is initialized, the `has_one` would attempt to destroy `comment_1`, triggering a `touch` on `post` from an association record that hasn't been committed to the database.
This removes the attempt to delete an associated `has_one` unless it’s persisted.
#37523 has a regression that ignore extra scoping in callbacks when
create on association relation.
It should respect `klass.current_scope` even when create on association
relation to allow extra scoping in callbacks.
Fixes#38741.
* Fix EagerLoadPolyAssocsTest setup
* EagerLoadPolyAssocsTest includes a Remembered module in multiple test
ActiveRecord classes. The module is supposed to keep track of records
created for each of the included classes individually, but it collects all
records for every class. This happens because @@remembered is defined on the
Remembered module and shared between the ActiveRecord classes. This only
becomes an issue for databases (like CockroachDB) that use random primary
keys instead of sequential ones by default.
* To fix the bug, we can make the remembered collection name unique per
ActiveRecord class.
* Update EagerLoadPolyAssocsTest test setup
* Instead of defining remembered as a class variable, we can define it as an
instance variable that will be unique to every class that includes the
Remembered module.