Commit Graph

1540 Commits

Author SHA1 Message Date
Steve Weber
ba99e6d400 Set inverse during has one autosave if necessary
pair: @rebeldroid12
2020-10-30 15:31:40 +00:00
Ryuta Kamizono
255a7ef7b0
Implicit scoping does no longer leak scope to class level querying methods
This is second attempt of #32380 and #35186.
2020-10-30 00:25:38 +00:00
Lee Quarella
c6f4656477 Only set the association target if there is no foreign key mismatch
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>
2020-10-26 13:53:08 -04:00
dmitry
50e4aaaf25
Prevent adding the same record twice on build when using source record in attribute when using has_many_inversing #40378
Record should be replaced via add_to_target, not added as a separate record to an array.
2020-10-13 19:24:02 +03:00
Akira Matsuda
7acee4d7c0 Association setter methods are defined as public methods 2020-10-02 22:22:10 +09:00
Akira Matsuda
e57d9202a7 Methods with association name are defined as public methods 2020-10-02 22:22:10 +09:00
Akira Matsuda
632cb80c1b includes, preload, joins, and eager_load are public methods 2020-10-02 21:57:42 +09:00
Akira Matsuda
3fc64f53a0 Association#klass is a public method 2020-10-02 20:56:50 +09:00
George Claghorn
4cf7559280 Destroy associations in a background job.
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>
2020-09-24 14:24:15 -04:00
Ryuta Kamizono
57da1d0188
Merge pull request #40106 from kamipo/where_references_association_name
Allow `where` references association names as joined table alias names
2020-08-31 11:46:30 +09:00
Rafael Mendonça França
7101489293
Fix the test based on the changes on #40104 2020-08-26 21:09:40 +00:00
Rafael França
42913f15d3
Merge pull request #39337 from p8/did-you-mean-for-inverse-of-not-found
Add DidYouMean for InverseOfAssociationNotFoundError
2020-08-26 17:04:48 -04:00
Ryuta Kamizono
999bf3d097 Allow where references association names as joined table alias names
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.
2020-08-26 10:07:13 +09:00
Petrik
504a9d5010 Rename horrible and dirty to puzzled and confused in test models
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.
2020-08-25 18:32:52 +02:00
Petrik
c639ef17ff Add DidYouMean for InverseOfAssociationNotFoundError
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>
2020-08-25 09:10:47 +02:00
Ryuta Kamizono
62cfbdf36e Support storing demodulized class name for polymorphic type
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.
2020-08-23 16:04:29 +09:00
Ryuta Kamizono
8162c88186
Merge pull request #40043 from kamipo/fix_eager_loading_duplicated_association_part2
Fix incorrect result when eager loading with duplicated through association with join scope Part 2
2020-08-15 06:54:47 +09:00
Eileen M. Uchitelle
c93f3f0226
Merge pull request #40046 from composerinteralia/renaming-man
Rename test models for inclusivity
2020-08-14 11:51:37 -04:00
Daniel Colson
d79519f2ff
Rename single letter variables
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`.
2020-08-14 11:37:09 -04:00
Daniel Colson
7f938cacba
Replace test Man with Human
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".
2020-08-14 11:37:09 -04:00
Ryuta Kamizono
62de28fbcd Fix incorrect result when eager loading with duplicated through association with join scope Part 2
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.
2020-08-14 14:34:44 +09:00
Ryuta Kamizono
10b36e81a3 Fix incorrect result when eager loading with duplicated through association with join scope
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.
2020-08-07 13:39:30 +09:00
Jonathan Hefner
5e2d3db4b5 Fix flakey test due to non-deterministic order
Example failure: https://buildkite.com/rails/rails/builds/70840#d213df74-a2e6-45e5-8302-d75a6c31f700/1021-1033

The test's assertion expects the authors to be ordered by ID, but that
order is not guaranteed without an explicit sort.
2020-07-27 10:53:12 -05:00
Ryuta Kamizono
1ac40f16c5 Move Arel attribute normalization into arel_table
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`.
2020-07-19 23:41:24 +09:00
Ryuta Kamizono
13160269fe Resolve attribute alias for counter cache column
The lack of ability for making this has resolved by #39496 and #39530.
2020-07-06 12:06:01 +09:00
Ryuta Kamizono
60b43ab0d7 More exercise tests for first nth and last on dirty collection 2020-06-22 15:01:42 +09:00
Ryuta Kamizono
4f95ff7840 Fix preloading for polymorphic association with custom scope
#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.
2020-05-26 04:07:28 +09:00
Ryuta Kamizono
42914820d9 Do not use object_id on Active Record object directly
Follow up to #38990.
2020-05-24 11:08:24 +09:00
Ryuta Kamizono
6a617cc61e Fix through association with source/through scope which has joins
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.
2020-05-22 04:05:27 +09:00
Ryuta Kamizono
489df7eb28 Fix through association to respect source scope for includes/preload
`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.
2020-05-21 07:40:01 +09:00
Petrik
9082364a25 Add DidYouMean for HasManyThroughAssociationNotFoundError
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
```
2020-05-19 08:27:08 +02:00
Petrik
3bc7756036 Use DidYouMean for AssociationNotFoundError
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
```
2020-05-17 21:32:19 +02:00
Ryuta Kamizono
8f365c5d04 Fix eager load with Arel joins to maintain the original joins order
#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.
2020-05-16 10:17:11 +09:00
Ryuta Kamizono
1c3e75bf09
Merge pull request #39268 from kamipo/fix_merging_multiple_left_joins
Fix left joins order when merging multiple left joins from different associations
2020-05-14 06:22:50 +09:00
Ryuta Kamizono
32a7ba91d9 Fix left joins order when merging multiple left joins from different associations
#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.
2020-05-13 22:48:48 +09:00
Ryuta Kamizono
71f0df943d Fix pluck to correctly type cast same column names and association columns
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.
2020-05-13 20:24:42 +09:00
Ryuta Kamizono
42daf01958
Merge pull request #39156 from bogdan/preloader-duplicate-object-ids
Ensure array passed to preloader has no duplicate records by object_id
2020-05-06 02:48:48 +09:00
Bogdan Gusiev
3d76110f63 Ensure array passed to preloader has no duplicate records by object_id
Fixes #39073
2020-05-05 20:30:13 +03:00
Eugene Kenny
072a962fa2
Merge pull request #39138 from jonathanhefner/flakey-destroyed_by_association-tests
Fix flakey destroyed_by_association tests
2020-05-05 01:51:09 +01:00
Jonathan Hefner
7f5deeeee6 Fix flakey destroyed_by_association tests
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>
2020-05-04 19:26:54 -05:00
Ryuta Kamizono
7d4cc56ef8 Fix rewhere to allow overwriting association queries 2020-05-04 20:05:41 +09:00
Ryuta Kamizono
55ef531576 Fix random CI failure due to non-deterministic order
https://buildkite.com/rails/rails/builds/68559#2decc846-c6b5-46f7-a805-426a8063d36c/1016-1027
2020-05-01 15:00:01 +09:00
Ryuta Kamizono
3de9669188
Merge pull request #39057 from kamipo/deprecate_in_clause_length
Deprecate `in_clause_length` in `DatabaseLimits`
2020-04-27 15:34:29 +09:00
Ryuta Kamizono
60ff119487 Fix typo 2020-04-27 15:30:00 +09:00
Ryuta Kamizono
c0ca7625ca Deprecate in_clause_length in DatabaseLimits
`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).
2020-04-27 01:09:09 +09:00
Ryuta Kamizono
e93f416abe Fix unscoping association scope on joins not to raise an error
#29589 changed merging scope order to allow to unscope default scopes on
association scope (#29611), but that caused a regression #38811 that
accidentally allow join constraint which is required.

```
% bin/test test/cases/associations/has_many_associations_test.rb -n test_unscopes_the_default_scope_of_associated_model_when_used_with_include
Run options: -n test_unscopes_the_default_scope_of_associated_model_when_used_with_include --seed 32978

# Running:

E

Error:
HasManyAssociationsTest#test_unscopes_the_default_scope_of_associated_model_when_used_with_include:
NoMethodError: undefined method `children' for nil:NilClass
    ~/rails/activerecord/lib/active_record/associations/join_dependency/join_association.rb:39:in `block in join_constraints'
    ~/rails/activerecord/lib/active_record/associations/join_dependency/join_association.rb:30:in `reverse_each'
    ~/rails/activerecord/lib/active_record/associations/join_dependency/join_association.rb:30:in `with_index'
    ~/rails/activerecord/lib/active_record/associations/join_dependency/join_association.rb:30:in `join_constraints'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:171:in `make_constraints'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:196:in `block in walk'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:196:in `each'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:196:in `flat_map'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:196:in `walk'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:90:in `block in join_constraints'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:87:in `each'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:87:in `flat_map'
    ~/rails/activerecord/lib/active_record/associations/join_dependency.rb:87:in `join_constraints'
    ~/rails/activerecord/lib/active_record/relation/query_methods.rb:1226:in `build_join_query'
    ~/rails/activerecord/lib/active_record/relation/query_methods.rb:1211:in `build_joins'
    ~/rails/activerecord/lib/active_record/relation/query_methods.rb:1091:in `build_arel'
    ~/rails/activerecord/lib/active_record/relation/query_methods.rb:1063:in `arel'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:419:in `block in limited_ids_for'
    ~/rails/activerecord/lib/active_record/relation.rb:867:in `skip_query_cache_if_necessary'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:419:in `limited_ids_for'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:398:in `apply_join_dependency'
    ~/rails/activerecord/lib/active_record/relation.rb:839:in `block in exec_queries'
    ~/rails/activerecord/lib/active_record/relation.rb:867:in `skip_query_cache_if_necessary'
    ~/rails/activerecord/lib/active_record/relation.rb:834:in `exec_queries'
    ~/rails/activerecord/lib/active_record/relation.rb:639:in `load'
    ~/rails/activerecord/lib/active_record/relation.rb:250:in `records'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:508:in `find_take'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:98:in `take'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:458:in `find_one'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:442:in `find_with_ids'
    ~/rails/activerecord/lib/active_record/relation/finder_methods.rb:69:in `find'
    ~/rails/activerecord/test/cases/associations/has_many_associations_test.rb:2689:in `block in <class:HasManyAssociationsTest>'

bin/test test/cases/associations/has_many_associations_test.rb:2683
```

Required join constraint should not be allowed to unscoping.

Fixes #38811.
2020-04-16 00:34:41 +09:00
Josh
ba3ef762fc Prevent has_one from touching parent record unless persisted
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.
2020-04-13 20:16:00 -05:00
Ryuta Kamizono
f64b5fb942 Allow extra scoping in callbacks when create on association relation
#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.
2020-04-10 16:10:49 +09:00
alimi
834f5414c3
Fix EagerLoadPolyAssocsTest setup (#38883)
* 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.
2020-04-07 10:14:17 +09:00
Eugene Kenny
ddaa24ac70 Sort results to fix nondeterministic test failures
https://buildkite.com/rails/rails/builds/67891#867b2766-7984-4280-90d6-7f0412e2d239/1015-1026
2020-03-29 05:30:25 +01:00