Skip to content

Commit 7152760

Browse files
reid-rigorafaelfranca
authored andcommitted
Strict loading using :n_plus_one_only does not eagerly load child associations.
Before: ```ruby person = Person.find(1) person.strict_loading!(mode: :n_plus_one_only) person.posts.first # SELECT * FROM posts WHERE person_id = 1; -- non-deterministic order ``` After: ```ruby person = Person.find(1) person.strict_loading!(mode: :n_plus_one_only) person.posts.first # this is 1+1, not N+1 # SELECT * FROM posts WHERE person_id = 1 ORDER BY id LIMIT 1; ``` Strict loading in `:n_plus_one_only` mode is designed to prevent performance issues when deeply traversing associations. It allows `Person.find(1).posts`, but _not_ `Person.find(1).posts.map(&:category)`. With this change, child associations are no longer eagerly loaded, to match intended behavior and to prevent non-deterministic order issues caused by calling methods like `first` or `last`. Fixes rails#49473.
1 parent d60a234 commit 7152760

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed

activerecord/CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
* Strict loading using `:n_plus_one_only` does not eagerly load child associations.
2+
3+
With this change, child associations are no longer eagerly loaded, to
4+
match intended behavior and to prevent non-deterministic order issues caused
5+
by calling methods like `first` or `last`. As `first` and `last` don't cause
6+
an N+1 by themselves, calling child associations will no longer raise.
7+
Fixes #49473.
8+
9+
Before:
10+
11+
```ruby
12+
person = Person.find(1)
13+
person.strict_loading!(mode: :n_plus_one_only)
14+
person.posts.first
15+
# SELECT * FROM posts WHERE person_id = 1; -- non-deterministic order
16+
person.posts.first.firm # raises ActiveRecord::StrictLoadingViolationError
17+
```
18+
19+
After:
20+
21+
```ruby
22+
person = Person.find(1)
23+
person.strict_loading!(mode: :n_plus_one_only)
24+
person.posts.first # this is 1+1, not N+1
25+
# SELECT * FROM posts WHERE person_id = 1 ORDER BY id LIMIT 1;
26+
person.posts.first.firm # no longer raises
27+
```
28+
29+
*Reid Lynch*
30+
131
* Allow `Sqlite3Adapter` to use `sqlite3` gem version `2.x`
232

333
*Mike Dalessio*

activerecord/lib/active_record/associations/collection_association.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def null_scope?
303303

304304
def find_from_target?
305305
loaded? ||
306-
owner.strict_loading? ||
306+
(owner.strict_loading? && owner.strict_loading_all?) ||
307307
reflection.strict_loading? ||
308308
owner.new_record? ||
309309
target.any? { |record| record.new_record? || record.changed? }

activerecord/lib/active_record/core.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,11 @@ def strict_loading_n_plus_one_only?
704704
@strict_loading_mode == :n_plus_one_only
705705
end
706706

707+
# Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
708+
def strict_loading_all?
709+
@strict_loading_mode == :all
710+
end
711+
707712
# Marks this record as read only.
708713
#
709714
# customer = Customer.first

activerecord/test/cases/strict_loading_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ def test_strict_loading_n_plus_one_only_mode_with_belongs_to
8686
end
8787
end
8888

89+
def test_strict_loading_n_plus_one_only_mode_does_not_eager_load_child_associations
90+
developer = Developer.first
91+
developer.strict_loading!(mode: :n_plus_one_only)
92+
developer.projects.first
93+
94+
assert_not_predicate developer.projects, :loaded?
95+
96+
assert_nothing_raised do
97+
developer.projects.first.firm
98+
end
99+
end
100+
89101
def test_strict_loading
90102
Developer.all.each { |d| assert_not d.strict_loading? }
91103
Developer.strict_loading.each { |d| assert_predicate d, :strict_loading? }

0 commit comments

Comments
 (0)