Railsで検索機能を実装 しよう:Ransack vs Elasticsearch

ruby on rails

Railsで検索機能を実装 することは、アプリケーションのユーザー体験を大きく向上させる重要な要素です。本記事では、Railsで検索機能を実装する際に役立つ代表的なライブラリ「Ransack」と「Elasticsearch」を徹底比較し、それぞれの特徴や実装方法、パフォーマンスの違いを分かりやすく解説します。


Ransackの概要と特徴

Ransackとは?

Ransackは、ActiveRecordベースの検索を簡単に実装できるRails専用のGemです。シンプルで直感的な構文を使い、フォームを通じてSQLクエリを自動生成します。

特徴

  • 手軽さ: Gemをインストールするだけで使用可能。
  • 簡単なカスタマイズ: 検索条件を柔軟に指定できる。
  • Railsに統合された動作: フォームやモデルと相性が良い。

実装例

Gemのインストール


# Gemfile
gem 'ransack'

モデルに検索可能なカラムを追加

Ransackは特別な設定なしにすぐに使用できますが、検索条件に含めたいカラムがデータベースに存在する必要があります。

検索フォームの作成

以下のコードは、名前と価格帯で商品を検索するフォームの例です。


<%= search_form_for @q, url: products_path, method: :get do |f| %>
  <div>
    <%= f.label :name_cont, "商品名に含まれる文字" %>
    <%= f.text_field :name_cont, class: "form-control" %>
  </div>
  <div>
    <%= f.label :price_gteq, "最低価格" %>
    <%= f.number_field :price_gteq, class: "form-control" %>
  </div>
  <div>
    <%= f.label :price_lteq, "最高価格" %>
    <%= f.number_field :price_lteq, class: "form-control" %>
  </div>
  <%= f.submit "検索", class: "btn btn-primary" %>
<% end %>

コントローラーの設定

フォームから送信された条件を受け取り、検索結果を取得します。


class ProductsController < ApplicationController
  def index
    @q = Product.ransack(params[:q])
    @products = @q.result(distinct: true).order(created_at: :desc)
  end
end

検索結果の表示

検索結果をビューに表示するためのコード例です。


<% @products.each do |product| %>
  <div>
    <h3><%= product.name %></h3>
    <p>価格: <%= product.price %></p>
  </div>
<% end %>

応用: カスタムスコープ

複雑な条件をモデルで定義することで再利用可能な検索ロジックを作成できます。


class Product < ApplicationRecord
  scope :expensive, -> { where('price > 10000') }
end

メリット

  • Railsに馴染みやすい
  • 軽量でシンプル
  • 小規模アプリ向け

デメリット

  • 複雑なクエリには対応が難しい
  • フルテキスト検索が苦手

Elasticsearchの概要と特徴

Elasticsearchとは?

Elasticsearchは、分散型の全文検索エンジンで、大規模データや高度な検索機能が必要な場面で活躍します。

特徴

  • フルテキスト検索: 曖昧検索や部分一致に対応。
  • 高速検索: 数百万件のデータでもパフォーマンスを維持。
  • 柔軟なカスタマイズ: 検索クエリやランキングロジックを詳細に設定可能。

実装例

Gemのインストール


# Gemfile
gem 'elasticsearch-model'
gem 'elasticsearch-rails'

モデルの設定

Elasticsearch用の設定を追加します。


class Product < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  settings do
    mappings dynamic: 'false' do
      indexes :name, type: :text, analyzer: 'standard'
      indexes :price, type: :float
    end
  end
end

データをインデックスに登録


rails c
> Product.import(force: true)

検索フォームの作成

フルテキスト検索用のシンプルなフォーム例です。


<%= form_with url: search_products_path, method: :get do |f| %>
  <div>
    <%= f.label :query, "検索キーワード" %>
    <%= f.text_field :query, class: "form-control" %>
  </div>
  <%= f.submit "検索", class: "btn btn-primary" %>
<% end %>

コントローラーの設定


class ProductsController < ApplicationController
  def search
    query = params[:query]
    @products = Product.search(query).records
  end
end

高度な検索クエリ

ElasticsearchのDSLを利用して柔軟な検索条件を定義可能です。


Product.search({
  query: {
    bool: {
      must: [
        { match: { name: "特定の商品" } },
        { range: { price: { gte: 5000 } } }
      ]
    }
  }
})

メリット

  • 高度な検索クエリが可能(部分一致、曖昧検索など)
  • 大量データでも高速な検索が可能

デメリット

  • セットアップが複雑
  • インフラリソースの負担が増加

比較表:Ransack vs Elasticsearch

特徴 Ransack Elasticsearch
シンプルさ ◎(簡単に導入可能) △(セットアップがやや複雑)
パフォーマンス △(中小規模アプリ向け) ◎(大規模データに対応)
機能性 △(基本的な検索機能) ◎(高度な検索が可能)
学習コスト ◎(Railsの知識で十分) △(Elasticsearchの知識が必要)
インフラ要件 ◎(特別な要件なし) △(Elasticsearchのセットアップ必要)

実際の利用例

Ransackの適用例

Ransackは、中小規模のアプリケーションに適したシンプルな検索機能を提供します。以下は、中小規模のECサイトの商品の検索機能の具体例です。

シナリオ

商品一覧ページで「商品名」「価格帯」「カテゴリー」を指定して検索できる機能を提供します。

実装例

商品検索フォームを作成し、検索結果を表示するコードを示します。


# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def index
    @q = Product.ransack(params[:q])
    @products = @q.result.includes(:category).order(created_at: :desc)
  end
end

# app/views/products/index.html.erb
<%= search_form_for @q, url: products_path, method: :get do |f| %>
  <div>
    <%= f.label :name_cont, "商品名" %>
    <%= f.text_field :name_cont, class: "form-control" %>
  </div>
  <div>
    <%= f.label :price_gteq, "最低価格" %>
    <%= f.number_field :price_gteq, class: "form-control" %>
  </div>
  <div>
    <%= f.label :price_lteq, "最高価格" %>
    <%= f.number_field :price_lteq, class: "form-control" %>
  </div>
  <div>
    <%= f.label :category_id_eq, "カテゴリー" %>
    <%= f.collection_select :category_id_eq, Category.all, :id, :name, include_blank: true, class: "form-control" %>
  </div>
  <%= f.submit "検索", class: "btn btn-primary" %>
<% end %>

<h2>検索結果</h2>
<% @products.each do |product| %>
  <div>
    <h3><%= product.name %></h3>
    <p>価格: <%= product.price %> 円</p>
    <p>カテゴリー: <%= product.category.name %></p>
  </div>
<% end %>

Elasticsearchの適用例

Elasticsearchは、大規模なデータセットや複雑な検索要件があるアプリケーションに適しています。以下は、大規模ECサイトで「フルテキスト検索」と「価格帯検索」を実現する具体例です。

シナリオ

ユーザーが商品名や説明文を部分一致で検索でき、さらに価格帯で絞り込む機能を提供します。

実装例

商品データをElasticsearchにインデックスし、検索を実行するコードを示します。


# app/models/product.rb
class Product < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  settings do
    mappings dynamic: 'false' do
      indexes :name, type: :text, analyzer: 'standard'
      indexes :description, type: :text, analyzer: 'standard'
      indexes :price, type: :float
    end
  end
end

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def search
    query = params[:query]
    min_price = params[:min_price].to_f
    max_price = params[:max_price].to_f

    @products = Product.search({
      query: {
        bool: {
          must: [
            { multi_match: { query: query, fields: %w[name description] } }
          ],
          filter: [
            { range: { price: { gte: min_price, lte: max_price } } }
          ]
        }
      }
    }).records
  end
end

# app/views/products/search.html.erb
<%= form_with url: search_products_path, method: :get do |f| %>
  <div>
    <%= f.label :query, "商品名または説明文" %>
    <%= f.text_field :query, class: "form-control" %>
  </div>
  <div>
    <%= f.label :min_price, "最低価格" %>
    <%= f.number_field :min_price, class: "form-control" %>
  </div>
  <div>
    <%= f.label :max_price, "最高価格" %>
    <%= f.number_field :max_price, class: "form-control" %>
  </div>
  <%= f.submit "検索", class: "btn btn-primary" %>
<% end %>

<h2>検索結果</h2>
<% @products.each do |product| %>
  <div>
    <h3><%= product.name %></h3>
    <p><%= product.description %></p>
    <p>価格: <%= product.price %> 円</p>
  </div>
<% end %>

どちらを選ぶべきか?

小規模でシンプルな検索が必要な場合はRansackを、大規模で高度な検索が必要な場合はElasticsearchを選ぶのが適切です。

結論

どちらも優れた選択肢ですが、プロジェクト規模や要件に応じて適切なライブラリを選択することが重要です。

コメント

タイトルとURLをコピーしました