Railsで検索機能を実装する際のアレコレ
最近、実務で簡単な検索機能を実装する機会があり、その時に調べたり、先輩からアドバイスをいただいたりしたので、その内容をまとめたいと思います。
まず検索機能について
ひとくちに検索機能といっても色々あると思いますが、今回はざっくり
- 完全一致、部分一致
- 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)
Discussion