バートリーのさいとうです。
今回は
【Rails】関連先のレコードが存在しないという条件で絞り込みを行う方法
というテーマを解説していきます。
関連先のレコードが存在しない、というのは以下のような状況です。
UserモデルとArticleモデルを例に取ります。
- User has many articles
- Article belongs to user
の、1対多の関係です。
class User < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :user
end
この関連付けがなされているときに、「Articleモデルを持っていないUserを絞り込みしたい」時にどのような処理を書けば良いのか、ということを解説していきます。
早速見ていきましょう。
(※Rails: 4.2で確認されている挙動です。後半でrails5, 6の補足を加えます。)
includesとwhere
この絞り込みに利用するのは、includesとwhereです。
includesは、N + 1問題を避けるために利用されるメソッドです。ざっくり説明しておくと、includesしておけば一つのUserを読み込んだ時に、関連付けされている全部のArticleモデルのレコードを、一つのSQL文で取得してくれます。
whereは、条件を指定して絞り込みを行うことができるメソッドです。ActiveRecord::Relationを返します。
では、実際のコードを見ていきましょう。
idにnilを指定する
結論から言うと、where句の中で、idにnilを指定します。
users = User.includes(:articles).where(:articles: {id: nil})
ポイントは2点です。
- 関連付けされているArticleモデルのidを検索条件にする
- idがnil、つまり存在しないという条件を付与する
これで、N + 1問題を解消しながらArticleモデルを持たないUserモデルを取得することができました。
Rails5, 6では?
これは、rails4.2で挙動が確認されています。
しかし、rails5, rails6ではより良い方法が提唱されているようです。
ここからは、同じ質問をされていたこちらのstackoverflowの記事の回答を元に、翻訳しながら5と6だとどのような記述になるのか補足していきます。
Rails5.0~
User.left_outer_joins(:articles).where(articles: { id: nil })
left_outer_joinsは、left_joinsのエイリアスです。rails5から利用できます。
left_joinsについては、こちらの記事がわかりやすいです。
このメソッドは
ownersテーブルが基準となる為、関連するcatsテーブルのデータがない場合(NULL)でも取得されます。
(owners→users, cats→articles)
この特徴を利用し、「idが存在しない」という条件を作成することができます。
SQLは以下の通りになります。
# SELECT "users".* FROM "users" LEFT OUTER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "articles"."id" IS NULL
Rails6.1~
User.where.missing(:articles)
すごい簡単になりましたね。詳細は以下の記事で解説されています。
SQLはleft_outer_joinsを利用している時と同じです。
まとめ
今回は、関連先のレコードが存在しないという条件で絞り込みを行う方法について解説しました。
バージョンが上がるにつれ、だんだんと簡略化されていくRailsの姿も見ることができました。
簡略化される分、中で何が起きているかわかりずらくなるんですけどね。矛盾。
このブログは、月に15本を目標に、実務から学んだプログラミングのあれこれを発信しています。
誰かのためになれば、嬉しいです。
最後まで読んでいただき、ありがとうございました。
それでは。
参考記事