ActiveRecord

【ActiveRecord】referencesメソッドの使い方

今回はActiveRecordのreferencesメソッドについて挙動を確認したので、それをまとめたいと思います。

referencesメソッドとは

API docには以下のように定義されていました。

指定されたtable_namesがSQL文字列によって参照されているため、個別にロードするのではなく、任意のクエリでJOINする必要があることを示すために使用します。

https://apidock.com/rails/ActiveRecord/QueryMethods/references

どういうことなのかを実際にメソッドを使いながら見ていきます。

記事一覧からコメントの内容で検索する例を見てみます。

Article.includes(:comments).where("comments.content = 'a'")
=>   Article Load (3.8ms)  SELECT `articles`.* FROM `articles` WHERE (comments.created_at > 31535999.99995604)
  Article Load (3.4ms)  SELECT `articles`.* FROM `articles` WHERE (comments.created_at > 31535999.99995604) /* loading for inspect */ LIMIT 11
#<Article::ActiveRecord_Relation:0x4164>

エラーになって取得できないことが分かるかと思います。これは検索の際にテーブル名がSQL文字列によって参照されているためです。アソシエーションによって参照するようにすると、取得できます。

Article.includes(:comments).where(comments: { content: 'a' })
=>   CACHE SQL (0.2ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`content` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `articles`.`is_public` AS t0_r5, `comments`.`id` AS t1_r0, `comments`.`content` AS t1_r1, `comments`.`article_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4, `comments`.`user_id` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `comments`.`content` = 'a'
[#<Article:0x0000ffff90c82108
  id: 1,
  title: "記事1",
  content: "a",
  created_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  updated_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  is_public: true>]

少し反れましたが、referencesメソッドを使うことで正常に検索することができます。

Article.includes(:comments).references(:comments).where("comments.content = 'a'")
=>   SQL (8.1ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`content` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `articles`.`is_public` AS t0_r5, `comments`.`id` AS t1_r0, `comments`.`content` AS t1_r1, `comments`.`article_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4, `comments`.`user_id` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE (comments.content = 'a')
[#<Article:0x0000ffff9096cf18
  id: 1,
  title: "記事1",
  content: "a",
  created_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  updated_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  is_public: true>]

referencesは比較条件を使うときなど、SQL文字列を使って検索したいケースなどで有用です。

Article.includes(:comments).references(:comments).where('comments.created_at > ?', Time.current - 1.year.ago)
=>   SQL (25.0ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`content` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `articles`.`is_public` AS t0_r5, `comments`.`id` AS t1_r0, `comments`.`content` AS t1_r1, `comments`.`article_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4, `comments`.`user_id` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE (comments.created_at > 31535999.99946196)
[#<Article:0x0000ffff90ce9f38
  id: 1,
  title: "記事1",
  content: "a",
  created_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  updated_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  is_public: true>]

この挙動については、Railsのソースコード中でも例を交えて解説してくれています。この辺りが丁寧でRailsはいいですね。

https://github.com/rails/rails/blob/HEAD/activerecord/lib/active_record/relation/query_methods.rb#L152

mergeメソッドとの併用

もう一つ僕が実務で見てきたreferencesメソッドの使いどころはmergeメソッドと一緒に使うケースです。
mergeメソッドは2つのクエリをマージして一つのSQLにするメソッドです。mergeメソッドの使用例を見てみます。

Article.includes(:comments).where(comments: {content: 'a'}).merge(Comment.where(user_id: 1))
=>   CACHE SQL (0.1ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`content` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `articles`.`is_public` AS t0_r5, `comments`.`id` AS t1_r0, `comments`.`content` AS t1_r1, `comments`.`article_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4, `comments`.`user_id` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id`
     WHERE `comments`.`content` = 'a' AND `comments`.`user_id` = 1
[#<Article:0x0000aaaaf93142b0
  id: 1,
  title: "記事1",
  content: "a",
  created_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  updated_at: Sat, 19 Feb 2022 16:23:48.060743000 JST +09:00,
  is_public: true>]

SQL文のWHERE句がWHERE comments.content = 'a' AND comments.user_id = 1となっていて、mergeメソッドによって条件が結合されているのが分かるかと思います。

このとき、where句で絞り込みがされておらずincludesしているだけの場合、テーブルが結合されていないので、mergeすることができません。

Article.includes(:comments).merge(Comment.where(user_id: 1))
=>   Article Load (1.0ms)  SELECT `articles`.* FROM `articles` WHERE `comments`.`user_id` = 1
  Article Load (1.2ms)  SELECT `articles`.* FROM `articles` WHERE `comments`.`user_id` = 1 /* loading for inspect */ LIMIT 11
#<Article::ActiveRecord_Relation:0x3520>

この場合、referencesメソッドを使うことで、mergeすることができます。

Article.includes(:comments).references(:comments).merge(Comment.where(user_id: 1))
=>   SQL (11.0ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`content` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `articles`.`is_public` AS t0_r5, `comments`.`id` AS t1_r0, `comments`.`content` AS t1_r1, `comments`.`article_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4, `comments`.`user_id` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `comments`.`user_id` = 1
[]

※ データベースをリセットした関係で結果が返ってきてませんが、正常に取得するSQLが走っているのが分かるかと思います。

このように、mergeメソッドと組み合わせて使うのが、referencesメソッドのもう一つのユースケースです。

まとめ

referencesメソッドを使うユースケースとしては以下の2点です。

  • includesしたテーブルのカラムをSQL文字列を使って検索をする場合に利用する
  • mergeメソッドでincludes先のテーブルを使い、かつWHERE句などを用いてテーブルを結合していない場合に利用する

まだ他のユースケース等あればコメント等で教えていただけるとうれしいです。

Railsにはまだまだ知らない便利なメソッドがたくさんあると思うので、もっと勉強していきたいと思いました。
ここまで読んでいただきありがとうございました。


ABOUT ME
sakai
三重出身の28歳。前職はメーカーで働いていて、プログラミングスクールに通って未経験からWeb業界に転職しました。Railsをメインで使っていて、AWSも少しできます。音楽を聞くこととYoutubeを見るのが好きです。最近はへきトラ劇場にハマってます

COMMENT

メールアドレスが公開されることはありません。