railsでアソシエーション先がないデータを検索する方法

バックエンド

はじめに

何気に必要になる時があり、検索の仕方もよくわからないので、残しておく。
色々試してみたらwhereを使って実現できた。

ただ、missingなる便利メソッドがそもそもあったので、そっちもまとめとく。
今後はmissing使おうね。

テーブル定義

User —< Post >—< Tag の構成

rails g model User name:string
rails g model Post title:string user:references
rails g model Tag name:string
rails g model post_tag post:references tag:references
class User < ApplicationRecord
  has_many :posts
end
class Post < ApplicationRecord
  belongs_to :user
  has_many :post_tags
  has_many :tags, through: :post_tags
end
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end
class Tag < ApplicationRecord
  has_many :post_tags
  has_many :posts, through: :post_tags
end

こんなデータを投入してます。

User.create(name: "食べる君")
User.create(name: "専門家の人")
User.create(name: "見る専マン")

Tag.create(name: "レビュー記事")
Tag.create(name: "案件記事")
Tag.create(name: "ブログ")
Tag.create(name: "画像添付あり")

User.find_by(name: "食べる君").posts.create(title: "チョコ食べてみた", tags: Tag.where(name: ["案件記事", "画像添付あり"]))
User.find_by(name: "食べる君").posts.create(title: "アイス食べてみた", tags: Tag.where(name: ["画像添付あり"]))
User.find_by(name: "専門家の人").posts.create(title: "はじめましてチョコの専門家です", tags: [])
User.find_by(name: "専門家の人").posts.create(title: "M社のチョコの美味しさについて", tags: Tag.where(name: ["レビュー記事"]))
User.find_by(name: "専門家の人").posts.create(title: "たまにはチョコ以外も食べてみたい", tags: Tag.where(name: ["ブログ"]))

やりかた

Postを持たないUserが欲しい ( 1 対 多 )

User.includes(:posts).where(posts: { id: nil })
SQL (0.8ms)  SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `posts`.`id` AS t1_r0, `posts`.`title` AS t1_r1, `posts`.`user_id` AS t1_r2, `posts`.`created_at` AS t1_r3, `posts`.`updated_at` AS t1_r4 FROM `users` LEFT OUTER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `posts`.`id` IS NULL                                                                
=> [#]

Tagを持たないPostが欲しい ( 多 対 多 )

Post.includes(:post_tags).where(post_tags: { tag_id: nil })
SQL (0.6ms)  SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`user_id` AS t0_r2, `posts`.`created_at` AS t0_r3, `posts`.`updated_at` AS t0_r4, `post_tags`.`id` AS t1_r0, `post_tags`.`post_id` AS t1_r1, `post_tags`.`tag_id` AS t1_r2, `post_tags`.`created_at` AS t1_r3, `post_tags`.`updated_at` AS t1_r4 FROM `posts` LEFT OUTER JOIN `post_tags` ON `post_tags`.`post_id` = `posts`.`id` WHERE `post_tags`.`tag_id` IS NULL
=> [#]

missingなるクラスメソッドがあるらしい

こんなにこねくり回さなくてもmissingというメソッドがあった
発行されるSQLは同じで、こっちの方が圧倒的にセマンティック

User.where.missing(:posts)
Post.where.missing(:tags)
タイトルとURLをコピーしました