👬

【Rails】ActiveRecord の merge メソッドについての解説

2024/06/12に公開

概要

ActiveRecord の merge メソッドは WHERE 句を追加、結合するために使われます。
複数の ActiveRecord リレーションを組み合わせることで、複雑な条件でも可読性を損なわずに書くことができます。

以下で使い方のパターンを紹介します。

merge メソッドの基本的な挙動

例えば以下のように、 ActiveRecord のクエリを 結合することができます。

# 元のコード
Article.where(title: "タイトル", body: "本文")

# merge を使ってクエリを結合するコード
title_query = Article.where(title: "タイトル")
body_query = Article.where(body: "本文")
title_query.merge(body_query)

# 1行で書くとこう
Article.where(title: "タイトル").merge(Article.where(body: "本文"))

このコードで発行される SQL は以下です。

SELECT
  `articles`.*
FROM
  `articles`
WHERE
  `articles`.`title` = 'タイトル' AND `articles`.`body` = '本文'

ちなみに上記のクエリは作成されますが実行はされません。
そのあたりの遅延評価についてはこちらで解説してますので、合わせてどうぞ。
https://zenn.dev/ebina_shohei/articles/1a4684ab47ea35

リレーションの結合

以下のようなモデルがあるとします。

class User < ApplicationRecord
  has_many :articles
end

class Article < ApplicationRecord
  belongs_to :user
end

「Hello World!」という title を持ったユーザーを検索したい場合、以下のように書けます。

title_query = Article.where(title: "Hello World!")
User.joins(:articles).merge(title_query)

# 1行で書くとこう
User.joins(:articles).merge(Article.where(title: "Hello World!"))
SELECT
  `users`.*
FROM
  `users`
INNER JOIN
  `articles`
ON
  `articles`.`user_id` = `users`.`id`
WHERE
  `articles`.`title` = 'Hello World!'

スコープを使ったリレーションの結合

Article に is_publish: true のレコードだけを取得できる scope が存在するとします。

class User < ApplicationRecord
  has_many :articles
end

class Article < ApplicationRecord
  belongs_to :user

   # scope を追加
  scope :published, -> { where(is_published: true) }
end

そのユーザーを取得したいとき、以下のように書けます。

active_articles = Article.published
User.joins(:articles).merge(active_articles)

# 1行で書くとこう
User.joins(:articles).merge(Article.published)
SELECT
  `users`.*
FROM
  `users`
INNER JOIN
  `articles`
ON
  `articles`.`user_id` = `users`.`id`
WHERE
  `articles`.`is_published` = TRUE

複数条件の組み合わせ

以下の複数条件を組み合わせるときも merge を使えます。

  • 20歳より上のユーザー
  • "Helllo World!"というタイトルの記事を持っている
  • その記事は公開されている
title_articles = Article.where(title: "Hello World!")
active_articles = Article.published
over_20_users = User.where("age > ?", 20)

User.joins(:articles)
    .merge(over_20_users)
    .merge(title_articles)
    .merge(active_articles)
SELECT
  `users`.*
FROM
  `users`
INNER JOIN
  `articles`
ON
  `articles`.`user_id` = `users`.`id`
WHERE
  (age > '20')
AND
  `articles`.`title` = 'Hello World!'
AND
  `articles`.`is_published` = TRUE

まとめ

merge を使って複雑な条件でも可読性を保ちつつ、コードを書いていきましょう!

Discussion