Commit Graph

21 Commits

Author SHA1 Message Date
Yasuo Honda 945aa87466 Address "NameError: Rails couldn't find a valid model for Club association"
This commit addresses CI failure of Active Record isolation tests.
https://buildkite.com/rails/rails/builds/79936

Follow up #41392

* Steps to reproduce
```ruby
$ cd activerecord
$ bin/test test/cases/associations/belongs_to_associations_test.rb test/cases/associations/has_many_associations_test.rb test/cases/associations/has_many_through_disable_joins_associations_test.rb test/cases/associations/has_one_associations_test.rb test/cases/connection_adapters/schema_cache_test.rb test/cases/inheritance_test.rb test/cases/migration_test.rb test/cases/store_test.rb test/cases/strict_loading_test.rb
```

Since the entire output is too long, here is the minimum case fixed by
this commit.

```ruby
$ cd activerecord
$ bin/test test/cases/associations/belongs_to_associations_test.rb:38
Using sqlite3
Run options: --seed 34180

E

Error:
BelongsToAssociationsTest#test_belongs_to:
NameError: Rails couldn't find a valid model for Club association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/reflection.rb:431:in `rescue in compute_class'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/reflection.rb:422:in `compute_class'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/reflection.rb:372:in `klass'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/reflection.rb:722:in `association_primary_key'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/reflection.rb:727:in `join_primary_key'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_row.rb:150:in `block in resolve_sti_reflections'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_row.rb:138:in `each_value'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_row.rb:138:in `resolve_sti_reflections'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_row.rb:92:in `fill_row_model_attributes'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_row.rb:70:in `initialize'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:36:in `new'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:36:in `block in build_table_rows_from'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:35:in `each'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:35:in `map'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:35:in `build_table_rows_from'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixture_set/table_rows.rb:18:in `initialize'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:692:in `new'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:692:in `table_rows'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:633:in `block (2 levels) in insert'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:632:in `each'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:632:in `block in insert'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:629:in `each'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:629:in `insert'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:615:in `read_and_insert'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/fixtures.rb:567:in `create_fixtures'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/test_fixtures.rb:268:in `load_fixtures'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/test_fixtures.rb:122:in `setup_fixtures'
    /home/yahonda/src/github.com/rails/rails/activerecord/lib/active_record/test_fixtures.rb:10:in `before_setup'

bin/test test/cases/associations/belongs_to_associations_test.rb:38

Finished in 0.083668s, 11.9520 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
$
```
2021-08-05 12:40:19 +09:00
Dinah Shi cec55aafb9 Convert strict_loading_mode from class attr to ivar
Strict loading mode `:n_plus_one_only` is only
supported on single records, not associations or
models. Using an ivar instead of class_attribute
ensures that this cannot be set globally. This
fixes a bug where setting `strict_loading_mode`
caused errors to be silent when
`strict_loading_by_default` is true.

Fixes #42576

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2021-06-24 11:54:40 -04:00
Jacopo ba8248caa0 Strict loading cascade down to middle records
`strict_loading` now cascade down to middle records of through
associations.

Fixes #42421
2021-06-16 10:47:17 +02:00
Jean Boussier e65042707e Make `action_on_strict_loading_violation` a module instance variable
Followup: https://github.com/rails/rails/pull/42442
2021-06-10 18:31:33 +02:00
eileencodes 3328cd1d69
Ensure `reload` re-applies preload values for strict loading
If you have an application that has strict_loading set and then call
`reload` that would cause the preload values to get lost and
applications would start throwing a stict loading violation error.

In order to fix this we capture the association cache and re-apply that
to the reloaded records to avoid the strict loading error. Of course if
you never preloaded your records, this will still raise a strict loading
violation.

This change also removes the `reload` definition from associations.rb
because we can get the same behavior when we reassign the association
cache.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2021-04-26 15:45:39 -04:00
Abhay Nikam 2629f48ced ActiveRecord#strict_loading! should return boolean instead of mode set.
The return type was changed in the PR #41704 after addition of mode
option. The current documentation is misleading since
documentation puropose strict_loading! would return boolean whereas
it returns the current mode set.

I can across this issue while debugging issue: #41827 and thought
this should be brought to the attention.

PR fixes the issue and would always return boolean based on
strict_loading is enabled or disabled.

```
user.strict_loading! # => true
user.strict_loading!(false) # => false
user.strict_loading!(mode: :n_plus_one_only) # => true
```
2021-04-15 10:22:18 +05:30
Ryuta Kamizono a0097cd701 Add test case for class level `strict_loading_mode` 2021-03-28 10:49:15 +09:00
Dinah Shi 5ec1cac9f2 Add n_plus_one_only mode to Core#strict_loading!
Add an optional mode argument to
Core#strict_loading! to support n_plus_one_only
mode. Currently, when we turn on strict_loading
for a single record, it will raise even if we are
loading an association that is relatively safe to
lazy load like a belongs_to. This can be helpful
for some use cases, but prevents us from using
strict_loading to identify only problematic
instances of lazy loading.

The n_plus_one_only argument allows us to turn
strict_loading on for a single record, and only
raise when a N+1 query is likely to be executed.
When loading associations on a single record,
this only happens when we go through a has_many
association type. Note that the has_many
association itself is not problematic as it only
requires one query. We do this by turning
strict_loading on for each record that is loaded
through the has_many. This ensures that any
subsequent lazy loads on these records will raise
a StrictLoadingViolationError.

For example, where a developer belongs_to a ship
and each ship has_many parts, we expect the
following behaviour:

  developer.strict_loading!(mode: :n_plus_one_only)

  # Do not raise when a belongs_to association
  # (:ship) loads its has_many association (:parts)
  assert_nothing_raised do
    developer.ship.parts.to_a
  end

  refute developer.ship.strict_loading?
  assert developer.ship.parts.all?(&:strict_loading?)
  assert_raises ActiveRecord::StrictLoadingViolationError do
    developer.ship.parts.first.trinkets.to_a
  end
2021-03-24 20:55:18 -04:00
Ryuta Kamizono 8c7883d3fc ✂️ [ci skip] 2021-03-22 04:46:11 +09:00
eileencodes 5e300f1121
Fix test name
I copied the previous test and forgot to remain it. This commit fixes
the test name.
2021-03-17 09:19:55 -04:00
eileencodes 226007daa1
Handle false in relation strict loading checks
Fixes: #41453
Closes: #41461

Previously when a model had strict loading set to true and then had a
relation set strict_loading to false the false wasn't considered when
deciding whether to raise/warn about strict loading.

Code example:

```ruby
class Dog < ActiveRecord::Base
  self.strict_loading_by_default = true

  has_many :treats, strict_loading: false
end
```

In the example, `dog.treats` would still raise even though
`strict_loading` was set to false. This is a bug effecting more than
Active Storage which is why I made this PR superceeding #41461. We need
to fix this for all applications since the behavior is a little
surprising. I took the test from ##41461 and the code suggestion from #41453
with some additions.

Co-authored-by: Radamés Roriz <radamesroriz@gmail.com>
2021-03-16 16:26:59 -04:00
Ayrton De Craene 2041279960 Allow to opt-out of `strict_loading` mode on a per-record base.
This is useful when strict loading is enabled application wide or on a
model level.
2021-01-20 15:41:13 +01:00
eileencodes d2378bd3d8
Improve `strict_loading` violation error message
If you're using `strict_loading` in an application the previous message
only told you what class was lazily loaded. This change updates the
error message to include both the class and the association name. This
is useful because now you won't need to lookup the association name in
the application, the error message will tell you exactly which symbol
preload is missing.
2021-01-13 15:05:51 -05:00
Eileen M. Uchitelle 5cb6338540
Merge pull request #40792 from ghiculescu/fixture-strict-loading
Ignore strict loading violations on instances loaded through fixtures
2020-12-10 20:03:00 -05:00
Alex Ghiculescu e100c398db Ignore strict loading violations on instances loaded through fixtures 2020-12-10 15:23:14 -06:00
eileencodes bda9bc013b
Fix strict loading on validations
We don't want strict loading to throw an error on validations since
those need to load the record in order to validate it.

This change check the owners's `validation_context`. If it's `nil` then
we know we're not currently validating an object in create/update. If
it's set then we're validating and want to skip raising for strict
loading.

Fixes: #40767
2020-12-09 13:42:24 -05:00
eileencodes 656c6faf78
Allow applications to change the behavior for a strict loading violation
Originally strict loading violations would always raise an error if
turned on for an association. This change allows for an application to
optionally log instead of raise. The behavior here is similar to how
strong parameters work. By default all environments will raise. An
application may want to use log in production while working on finding
all lazily loaded associations.

Set `config.active_record.action_on_strict_loading_violation` to `:log`
in your application to log instead of raise.
2020-11-02 13:01:50 -05:00
bogdanvlviv 69bf870c50
Add `ActiveRecord::Base.strict_loading_by_default` and `ActiveRecord::Base.strict_loading_by_default=`.
This will allow to enable/disable strict_loading mode by default for a model.
The configuration's value is inheritable by subclasses, but they can override that value and
it will not impact parent class:

```ruby
class Developer < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :projects
end

dev = Developer.first
dev.projects.first
\# => ActiveRecord::StrictLoadingViolationError Exception: Developer is marked as strict_loading and Project cannot be lazily loaded.
```

What is great about this feature that it could help users to nip N+1 queries in
the bud, especially for fresh applications, by setting
`ActiveRecord::Base.strict_loading_by_default = true` / `config.active_record.strict_loading_by_default = true`.
That is also a great way to prevent new N+1 queries in the existing applications
after all the N+1 queries are eliminated.
(See https://guides.rubyonrails.org/v6.0/active_record_querying.html#eager-loading-associations,
https://github.com/seejohnrun/prelude for details on how to fight against N+1 queries).

Related to https://github.com/rails/rails/pull/37400, https://github.com/rails/rails/pull/38541
2020-06-01 12:25:25 +03:00
bogdanvlviv 1ff8479688
Clarify docs about strict loading and add a test case for `strict_loading!`
This commit does the following:
  - Hides `StrictLoadingScope` on API doc(https://edgeapi.rubyonrails.org/)
  - Documents `ActiveRecord::Base#strict_loading?` and `ActiveRecord::Base#strict_loading!`
    methods.
  - Adds the test case for `ActiveRecord::Base#strict_loading!` since it is
    a public API.
2020-05-31 11:26:51 +03:00
Kevin Deisz b8ae104318
Support strict_loading on association declarations 2020-02-21 13:11:24 -05:00
eileencodes dbb92f8a77
Add `strict_loading` mode to prevent lazy loading
Add `#strict_loading` to any record to prevent lazy loading of associations.
`strict_loading` will cascade down from the parent record to all the
associations to help you catch any places where you may want to use
`preload` instead of lazy loading. This is useful for preventing N+1's.

Co-authored-by: Aaron Patterson <aaron.patterson@gmail.com>
2020-02-20 08:32:48 -05:00