ransacker を利用したカスタム検索機能の実装
この記事では、Rails アプリケーションにおいて、テーブルに直接存在しない計算結果や集計値 を検索条件として利用する方法をご紹介します。具体的には、Book
モデルに関連付けられた Post
の件数を基に検索を行う例を取り上げます。
はじめに
Rails で検索機能を実装する際、ransack
という Gem を利用しました。
そこで、テーブルのカラムにない値で検索をする必要があり、それを達成するためにはransackerを利用すればできるということを学びました。学んだ内容を記事にしたいと思います。
ちなみに、ransackで検索機能をつくるのは以下の記事を参考にしました。
参考記事
環境
- Ruby: 3.2.4
- Ruby on Rails: 7.1.2
ransacker とは?
ransacker の目的
Ransack は Rails アプリケーションに柔軟な検索機能を提供する Gem です。しかし、標準状態ではテーブルに存在するカラムのみが検索対象となります。
そこで、ransacker
を利用することで、以下のようなケースに対応可能です。
- 計算結果や集計値を検索条件に含めたい場合
- テーブルのカラム以外のデータを検索に利用したい場合
この記事では、Book
に関連する Post
の件数(投稿数)を検索対象とする例を取り上げます。
データベースの構成
今回のサンプルでは、Book
と Post
が 1対多 (has_many/belongs_to) の関係になっています。
app/models/book.rb
class Book < ApplicationRecord
has_many :posts, dependent: :destroy
end
app/models/post.rb
class Post < ApplicationRecord
belongs_to :book
end
カスタム検索条件の実装
モデル内に ransacker
メソッドを定義することで、仮想的な属性を検索条件として追加できます。以下のコード例では、posts_count
という仮想属性を定義し、Book
に紐付く Post
の件数を取得する SQL サブクエリを利用しています。
# app/models/book.rb
class Book < ApplicationRecord
has_many :posts, dependent: :destroy
ransacker :posts_count do
Arel.sql('(
SELECT COUNT(posts.id)
FROM posts
WHERE posts.book_id = books.id
)')
end
end
この定義により、ransack を使った検索時に posts_count
を条件として指定できるようになります。たとえば、投稿数が特定の数以上の書籍を抽出するなどの検索が可能です。
おわりに
今回の実装例では、SQL のサブクエリを利用して仮想属性を定義しましたが、データ量が増加するとパフォーマンス面で課題が出る可能性があります。
そのため、将来的に投稿数での検索を多用する場合は、posts_count
カラムを用意し、投稿作成や削除時にカウントを更新する などの対応が良さそうです。