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]]
概要
原著者の許諾を得て翻訳・公開いたします。