We often come across cases in our Rails application where we want to negate our
where
clause conditions.
Let’s say we have a system that requires users to verify both their email and phone number.
We add two columns email_verified
and phone_verified
to our User model for verifying the account.
A user is verified only if both the email and phone of the user are verified by the system.
Before
Before Rails 7,
we would add verified
and unverified
scopes to our User
model
to get the details of verified and unverified users in our system.
class User < ApplicationRecord
scope :verified, -> { where(email_verified: true, phone_verified: true) }
scope :unverified, -> { where.not(email_verified: true, phone_verified: true) }
scope :with_verified_email, -> { where(email_verified: true) }
scope :with_unverified_email, -> { where.not(email_verified: true) }
end
User.verified
# SELECT "users".* FROM "users" WHERE "users"."email_verified" = $1 AND "users"."phone_verified" = $2 /* loading for inspect */ LIMIT $3 [["email_verified", true], ["phone_verified", true], ["LIMIT", 11]]
User.unverified
# SELECT "users".* FROM "users" WHERE NOT ("users"."email_verified" = $1 AND "users"."phone_verified" = $2) /* loading for inspect */ LIMIT $3 [["email_verified", true], ["phone_verified", true], ["LIMIT", 11]]
User.with_verified_email
# SELECT "users".* FROM "users" WHERE "users"."email_verified" = $1 /* loading for inspect */ LIMIT $2 [["email_verified", true], ["LIMIT", 11]]
User.with_unverified_email
# SELECT "users".* FROM "users" WHERE "users"."email_verified" != $1 /* loading for inspect */ LIMIT $2 [["email_verified", true], ["LIMIT", 11]]
As seen above, we have a verified
scope that checks if email_verified
and phone_verified
columns are true.
But to fetch unverified
users we had to introduce another scope with the where.not
clause.
This exposes the where.not
method which can be kept internal to Rails code.
Similarly, we have added separate scopes for users with verified and unverified emails.
After
Rails 7
adds
#invert_where
method to ActiveRecord that will invert all scope conditions.
Instead of creating unverified
and with_unverified_email
scopes with negating conditions,
we can just chain invert_where
to verified
and with_verified_email
scopes respectively as shown below.
class User < ApplicationRecord
scope :verified, -> { where(email_verified: true, phone_verified: true) }
scope :with_verified_email, -> { where(email_verified: true) }
end
User.verified
# SELECT "users".* FROM "users" WHERE "users"."email_verified" = $1 AND "users"."phone_verified" = $2 /* loading for inspect */ LIMIT $3 [["email_verified", true], ["phone_verified", true], ["LIMIT", 11]]
User.verified.invert_where
# SELECT "users".* FROM "users" WHERE NOT ("users"."email_verified" = $1 AND "users"."phone_verified" = $2) /* loading for inspect */ LIMIT $3 [["email_verified", true], ["phone_verified", true], ["LIMIT", 11]]
User.with_verified_email
# SELECT "users".* FROM "users" WHERE "users"."email_verified" = $1 /* loading for inspect */ LIMIT $2 [["email_verified", true], ["LIMIT", 11]]
User.with_verified_email.invert_where
# SELECT "users".* FROM "users" WHERE "users"."email_verified" != $1 /* loading for inspect */ LIMIT $2 [["email_verified", true], ["LIMIT", 11]]