Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Railsの技: 関連先レコードがないデータをwhere.missingで検索する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

#missingはRails 6.1で追加された機能です。

参考: 週刊Railsウォッチ(20190513)

Railsの技: 関連先レコードがないデータをwhere.missingで検索する(翻訳)

否定は一般には立証できませんが(訳注: 消極的事実の証明)、データベースに否定のクエリを投げることについてはどうでしょうか。クエリはデータを検索するために書くのが普通ですが、逆にデータが「存在しない」ことを検出するためにクエリを書くこともあります。

生SQLで言うなら、LEFT OUTER JOINNULLチェックを組み合わせることで、特定の関連付けを持たないレコードを検出できます。

利用法

Railsでは、上のようなSQLの概念をActive Recordで直接適用できます。

以下のモデルがあるとしましょう。

class Account < ApplicationRecord
  has_many :recovery_email_addresses
end

リカバリーメールのバックアップが設定されていないAccountを探索したいのであれば、以下のようなクエリを書くだけで問題なくできます。

Account.left_joins(:recovery_email_addresses).where(recovery_email_addresses: { id: nil })

# SELECT "accounts".* FROM "accounts" LEFT OUTER JOIN "recovery_email_addresses" ON "recovery_email_addresses"."account_id" = "accounts"."id" WHERE "recovery_email_addresses"."id" IS NULL

しかしこれではコードが長くなります。Rails 6.1からは、同じクエリを以下のようにずっと簡潔に書けるようになりました。

Account.where.missing(:recovery_email_addresses)

# SELECT "accounts".* FROM "accounts" LEFT OUTER JOIN "recovery_email_addresses" ON "recovery_email_addresses"."account_id" = "accounts"."id" WHERE "recovery_email_addresses"."id" IS NULL

生成されるSQLは同一になりますし、コードもずっと読みやすくなります。以下のようにbelongs_toリレーションシップでも同じことができます。

class Contract < ApplicationRecord
  belongs_to :promiser, class_name: "User"
  belongs_to :promisee, class_name: "User"
  belongs_to :beneficiary, optional: true, class_name: "User"
end

Contract.where.missing(:promiser) # promiserがないcontact
Contract.where.missing(:promiser, :beneficiary) # promiserもbeneficiaryもないcontact

以下のようにmissingを通常のActive Recordメソッドチェーンと組み合わせることもできます。

Contact.where("amount > ?", 1200).where.missing(:promiser)
Contact.where(signed: true).where.missing(:beneficiary)

参考資料

関連記事

Rails 6.1: 孤立化したレコードのリストを取れる’missing’クエリメソッドが追加(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。