Skip to content

Commit a6099ed

Browse files
Merge pull request rails#49570 from seanpdoyle/excluding-select-primary-key
Optimize `ActiveRecord::QueryMethods.excluding`
2 parents bbc6230 + 5aa611a commit a6099ed

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

activerecord/lib/active_record/relation/query_methods.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,9 @@ def uniq!(name)
14531453
# Post.excluding(post_one, post_two)
14541454
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
14551455
#
1456+
# Post.excluding(Post.drafts)
1457+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1458+
#
14561459
# This can also be called on associations. As with the above example, either
14571460
# a single record of collection thereof may be specified:
14581461
#
@@ -1468,14 +1471,15 @@ def uniq!(name)
14681471
# is passed in) are not instances of the same model that the relation is
14691472
# scoping.
14701473
def excluding(*records)
1474+
relations = records.extract! { |element| element.is_a?(Relation) }
14711475
records.flatten!(1)
14721476
records.compact!
14731477

1474-
unless records.all?(klass)
1478+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
14751479
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
14761480
end
14771481

1478-
spawn.excluding!(records)
1482+
spawn.excluding!(records + relations.flat_map(&:ids))
14791483
end
14801484
alias :without :excluding
14811485

activerecord/test/cases/excluding_test.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,37 @@ def test_result_set_does_not_include_collection_of_excluded_records
2222
assert_not_includes relation, posts(:thinking)
2323
end
2424

25+
def test_result_set_does_not_include_collection_of_excluded_records_from_a_query
26+
query = Post.where(id: @post)
27+
28+
assert_sql(/SELECT #{Regexp.escape Post.connection.quote_table_name("posts.id")} FROM/) do
29+
records = Post.excluding(query).to_a
30+
31+
assert_not_includes records, @post
32+
end
33+
end
34+
35+
def test_result_set_does_not_include_collection_of_excluded_records_from_a_loaded_query
36+
query = Post.where(id: @post).load
37+
38+
records = assert_queries 1 do
39+
Post.excluding(query).to_a
40+
end
41+
42+
assert_not_includes records, @post
43+
end
44+
45+
def test_result_set_does_not_include_collection_of_excluded_records_and_queries
46+
thinking = posts(:thinking)
47+
48+
records = assert_queries 2 do
49+
Post.excluding(@post, Post.where(id: thinking)).to_a
50+
end
51+
52+
assert_not_includes records, @post
53+
assert_not_includes records, thinking
54+
end
55+
2556
def test_result_set_through_association_does_not_include_single_excluded_record
2657
comment_greetings, comment_more_greetings = comments(:greetings, :more_greetings)
2758

@@ -38,6 +69,30 @@ def test_result_set_through_association_does_not_include_collection_of_excluded_
3869
assert_not_includes relation, comment_more_greetings
3970
end
4071

72+
def test_result_set_through_association_does_not_include_collection_of_excluded_records_from_a_relation
73+
relation = @post.comments
74+
75+
assert_sql(/SELECT #{Regexp.escape Comment.connection.quote_table_name("comments.id")} FROM/) do
76+
records = Comment.excluding(relation).to_a
77+
78+
assert_not_empty records
79+
assert_not_empty @post.comments
80+
assert_empty records.intersection(@post.comments.to_a)
81+
end
82+
end
83+
84+
def test_result_set_through_association_does_not_include_collection_of_excluded_records_from_a_loaded_relation
85+
relation = @post.comments.load
86+
87+
records = assert_queries 1 do
88+
Comment.excluding(relation).to_a
89+
end
90+
91+
assert_not_empty records
92+
assert_not_empty @post.comments
93+
assert_empty records.intersection(@post.comments.to_a)
94+
end
95+
4196
def test_does_not_exclude_records_when_no_arguments
4297
assert_no_excludes Post.excluding
4398
assert_no_excludes Post.excluding(nil)

0 commit comments

Comments
 (0)