🔍

Railsで検索機能を実装する際のアレコレ

2023/06/04に公開

最近、実務で簡単な検索機能を実装する機会があり、その時に調べたり、先輩からアドバイスをいただいたりしたので、その内容をまとめたいと思います。

まず検索機能について

ひとくちに検索機能といっても色々あると思いますが、今回はざっくり

  1. 完全一致、部分一致
  2. AND検索、OR検索

の軸で4つに分類して考えたいと思います。

以下のようなデータがあるとします。(値はテキトーです)

id name is_titan hobby
1 ハンジ ゾエ false 巨人観察
2 エレン イェーガー true 巨人の駆逐
3 エルビン スミス false 歴史の勉強
4 ミカサ アッカーマン false エレンを守る
5 アルミン アルレルト true 勉強

完全一致でAND検索

こちらは言わずもがな、ActiveRecordを使った方が良いですね。

User.where(name: 'ハンジ ゾエ').where(is_titan: false)
# SELECT `users`.* FROM `users` WHERE `users`.`name` = 'ハンジ ゾエ' AND `users`.`is_titan` = FALSE
実行結果
# [#<User:
  id: 1,
  name: "ハンジ ゾエ",
  is_titan: false,
  hobby: "巨人観察">]

ActiveRecord以外を使ったりしないと思うので、Arelなどの別の方法はスキップ🏃💨

部分一致でAND検索

ActiveRecord(SQL直書きパターン)

メリット: 同じwhere句で繋げられて、コードに統一感が出る(可読性)
デメリット: SQLをハードコーティングになる

User.where("name LIKE ?", "%ア%").where("hobby LIKE ?", "%勉強%")
# SELECT `users`.* FROM `users` WHERE (name LIKE '%ア%') AND (hobby LIKE '%勉強%')
実行結果
# [#<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

Arelを使うパターン

メリット: where句で繋げつつ、SQLのハードコーディングにならない
デメリット: 学習コストが高い

name_matches = User.arel_table[:name].matches("%ア%")
hobby_matches = User.arel_table[:hobby].matches("%勉強%")

User.where(name_matches.and(hobby_matches))
# SELECT `users`.* FROM `users` WHERE `users`.`name` LIKE '%ア%' AND `users`.`hobby` LIKE '%勉強%'
実行結果
# [#<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

完全一致でOR検索

ActiveRecord(orメソッドを使うパターン)

メリット: 発行されるSQLが直感的にわかりやすい
デメリット: .where()に統一できない

User.where(is_titan: true).or(User.where(name: 'ミカサ アッカーマン'))
# SELECT `users`.* FROM `users` WHERE (`users`.`is_titan` = TRUE OR `users`.`name` = 'ミカサ アッカーマン')
実行結果
[#<User:
  id: 2,
  name: "エレン イェーガー",
  is_titan: true,
  hobby: "巨人の駆逐">,
 #<User:
  id: 4,
  name: "ミカサ アッカーマン",
  is_titan: false,
  hobby: "エレンを守る">,
 #<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

ActiveRecord(SQL直書きパターン)

メリット: 同じwhere句で繋げられて、コードに統一感が出る(可読性)
デメリット: SQLをハードコーティングになる

User.where("is_titan = ? OR name = ?", true, 'ミカサ アッカーマン')
# SELECT `users`.* FROM `users` WHERE (is_titan = '1' OR name = 'ミカサ アッカーマン')
# orメソッドとは、微妙にSQLクエリが違う
実行結果
[#<User:
  id: 2,
  name: "エレン イェーガー",
  is_titan: true,
  hobby: "巨人の駆逐">,
 #<User:
  id: 4,
  name: "ミカサ アッカーマン",
  is_titan: false,
  hobby: "エレンを守る">,
 #<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

Arelを使うパターン

メリット: where句で繋げつつ、SQLのハードコーディングにならない
デメリット: 学習コストが高い

name_condition = User.arel_table[:name].eq("ミカサ アッカーマン")
is_titan_condition = User.arel_table[:is_titan].eq(true)

User.where(name_condition.or(is_titan_condition))
# SELECT `users`.* FROM `users` WHERE `users`.`age` = 19 AND (`users`.`name` = 'ミカサ アッカーマン' OR `users`.`is_titan` = TRUE)
実行結果
[#<User:
  id: 2,
  name: "エレン イェーガー",
  is_titan: true,
  hobby: "巨人の駆逐">,
 #<User:
  id: 4,
  name: "ミカサ アッカーマン",
  is_titan: false,
  hobby: "エレンを守る">,
 #<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

部分一致OR検索

SQL直書きパターン

メリット: 同じwhere句で繋げられて、コードに統一感が出る(可読性)
デメリット: SQLをハードコーティングになる

User.where("name LIKE ? OR hobby LIKE ?", "%ア%", "%勉強%")
# もしくは
User.where("name LIKE ?", "%ア%").or(User.where("hobby LIKE ?", "%勉強%"))

# SELECT `users`.* FROM `users` WHERE (name LIKE '%ア%' OR hobby LIKE '%勉強%')
# 発行されるSQLは同じ
実行結果
[#<User:
  id: 3,
  name: "エルビン スミス",
  is_titan: false,
  hobby: "歴史の勉強">,
 #<User:
  id: 4,
  name: "ミカサ アッカーマン",
  is_titan: false,
  hobby: "エレンを守る">,
 #<User:
  id: 6,
  name: "アルミン アルベルト",
  is_titan: true,
  hobby: "勉強">]

Arelを使うパターン

メリット: where句で繋げつつ、SQLのハードコーディングにならない
デメリット: 学習コストが高い

name_condition = User.arel_table[:name].matches("%ア%")
hobby_condition = User.arel_table[:hobby].matches("%勉強%")

User.where(name_condition.or(hobby_condition))
# SELECT `users`.* FROM `users` WHERE (`users`.`name` LIKE '%ア%' OR `users`.`hobby` LIKE '%勉強%')
実行結果
[#<User:
  id: 3,
  name: "エルビン スミス",
  is_titan: false,
  hobby: "歴史の勉強">,
 #<User:
  id: 4,
  name: "ミカサ アッカーマン",
  is_titan: false,
  hobby: "エレンを守る">,
 #<User:
  id: 6,
  name: "アルベルト アルミン",
  is_titan: true,
  hobby: "勉強">]

まとめ

個人的には、以下のような使い分けが良いかなと考えています。

AND検索 OR検索
完全一致 ActiveRecordを使う Arel か orメソッド
部分一致 Arelを使う Arelを使う

完全一致のOR検索で、プロジェクトに応じてどちらにするか決めた方が良いかなと思いました。
私が参画していたプロジェクトでは、検索は全てwhereで揃えて、可読性を重視しているような形だったので、積極的にArelを用いていました。(以下、コード例)

def index
  @user = User.
    .where(〇〇_condition)
    .where(△△_condition)
    .where(xx_condition)
end

def 〇〇_condition
end

...

部分一致の検索では、Arel一択ではないかと思っています。
理由は以下です。

  • (部分一致のOR検索の場合)orメソッドが使えない
  • SQLのハードコーディングはアンチパターン
    • 可読性が悪くなる
    • SQLインジェクションなどのセキュリティリスクが高まる可能性がある
    • ORMを用いてDBの種類による差異を吸収する意味がなくなる

Arelは学習コストが高いので、初めて学習する方には以下のサイトがおすすめです。(Arelに限らず、TechRachoさんにはRails関連でいつも助けてもらってますmm)
https://techracho.bpsinc.jp/kazz/2022_12_08/125126

Discussion