Rails 7のActive Recordにinvert_whereメソッドが追加される(翻訳)
Railsアプリケーションでwhere句の条件の否定を取りたくなるケースがよくあります。
たとえば、ユーザーのメールアドレスと電話番号の両方を検証する必要があるシステムがあるとします。アカウントを検証するために、Userモデルに2つのカラムemail_verifiedとphone_verifiedを追加します。
ユーザーは、システムによってユーザーのメールアドレスと電話番号の両方が検証された場合にのみ検証済みとなります。
変更前
Rails 7以前は、システム内の検証済みユーザーや未検証ユーザーの詳細を取得するために、Userモデルにverifiedスコープとunverifiedスコープを追加していました。
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]]
上のように、verifiedというスコープでemail_verifiedカラムとphone_verifiedカラムがtrueであるかどうかをチェックしています。しかし、unverifiedのユーザーを取得するためにwhere.not句を用いるスコープも別途導入する必要があります。こうしてwhere.notを使うメソッドがRailsコード内で公開されてしまいます。
メールアドレスについても同様に、検証済みのメールを持つユーザーと、未検証のメールアドレスを持つユーザーのそれぞれについてスコープを追加することになります。
変更後
Rails 7に、すべてのスコープ条件を反転させる#invert_whereメソッドが ActiveRecordに追加されました(#40249)。
否定条件を持つunverifiedスコープと with_unverified_emailスコープを両方作成する代わりに、以下のようにverifiedスコープと with_verified_emailスコープにそれぞれinvert_whereをチェインできます。
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]]
概要
原著者の許諾を得て翻訳・公開いたします。