Rails 7
adds
#excluding
method for an ActiveRecord::Relation
to exclude specific record (or collection of records)
from the resulting relation.
We come across cases in our Rails application, where we need to exclude a few set of records
when fetching data.
In such cases, we can use the excluding
method to filter out unwanted records.
Before
Rails 4.0 added where.not
to exclude a record or set of records from queries.
We pass our exclude conditions in the not
clause.
To ignore a particular post with a specific id we can use the below query.
Post.where.not(id: 1)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
To ignore the collection of records we can pass our condition in the not
clause as below.
Post.where.not(id: [1, 2])
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2).
where.not
can also be used on associations.
Let’s say we have Post
and Comment
models.
We want to ignore few comments on a particular post.
We chain where.not
clause to post.comments
query as shown below.
post = Post.find(1)
post.comments.where.not(id: 1)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 1
post.comments.where.not(id: [1, 2])
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" NOT IN (1, 2)
After
Rails 7 added #excluding
method which serves the same purpose as where.not
.
But unlike where.not
we pass objects to the excluding
method instead of object attributes.
To ignore a particular post with a specific id we can use the below query.
post = Post.find(1)
Post.excluding(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
To exclude the collection of posts we can pass its objects to the excluding
method as below
post_one = Post.find(1)
post_two = Post.find(2)
Post.all.excluding(post_one, post_two)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
Similar to where.not
we can use excluding
on associations as shown below
post_one = Post.find(1)
# And on associations
comment_one = Comment.find_by(id: 1, post: post_one)
comment_two = Comment.find_by(id: 2, post: post_one)
post_one.comments.excluding(comment_one)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 1
post_one.comments.excluding(comment_one, comment_two)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" NOT IN (1, 2)
When no condition is passed to the excluding
clause it returns all records.
# same as Post.all
Post.all.excluding
# SELECT "posts".* FROM "posts"
NOTE
An ArgumentError will be raised if either no records are specified, or if any of the records in the collection (if a collection is passed in) are not instances of the same model that the relation is scoping.
Post.excluding(1)
ArgumentError (You must only pass a single or collection of Post objects to #excluding.)
user = User.find(1)
Post.excluding(user)
ArgumentError (You must only pass a single or collection of Post objects to #excluding.)
We can also use the alias method #without
in-place of #excluding
.
post = Post.find(1)
Post.without(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1