LoginSignup
11
19

More than 5 years have passed since last update.

Ransackの検索をElasticsearchに置き換える

Posted at

概要

  • Elasticsearchを使って検索機能を強化しました
  • 個人でHerokuで運用しているサイトのransckを使った全文検索をElasticsearchに置き換えました
  • Elasticsearchは herokuのaddon Bonsai Elasticsearchを使いました

環境

  • herokuでrailsアプリを運用している
  • 環境変数を dot-env で管理している。

手順

HerokuにBonsaiを入れる

https://elements.heroku.com/addons/bonsai から Install Bonsai Elasticsearch を押すだけです。

image.png

Bonsaiは件数が少なければ無料で使えます。

image.png

Railsで検索できるようにする

elasticsearchのgemを入れる

こちら を参照。

環境変数にさきほど取得したBonsai ElasticsearchのURLを書く(下図参照)。

BONSAI_URL=https://user:password@example-acme-development.us-east.bonsaisearch.net

下図のRead & WriteのURLを上記に入れてください。

Image from Gyazo

  • elasticsearchのgemを入れる
gem 'bonsai-elasticsearch-rails'

ransackと共存できるようにする

既存の検索はransackをつかっています。そこでモデルに生える search methodが bonsai-elasticsearch-rails で生やされる search
とバッティングしないように対処します。

RansackのsearchとElasticSearchのsearchがコンフリクトするのでその対応をみて、
こちらの方法をやりました。

config/initializers/ransack.rb
Ransack::Adapters::ActiveRecord::Base.class_eval('remove_method :search')

これでOK

activerecord-importと共存できるようにする

ransackと同様にactiverecord-importのimportもバッティングします。

ググってactiverecord-importとelasticsearch-railsでメソッドが被る問題 がありましたので、これを参考に config/application.rb を編集すればOKです。

なお、これにより、activerecord-importの import メソッドは bulk_insert として呼び出すことになるので、注意してください。

検索用のmodelのconcernを書く

こちらのElasticsearchを使ったRailsサンプルアプリケーションの作成のサイトに従ってやりました。一部変更した箇所のみ説明します。

マッピングは string ではなく text に変更しました。https://dev.classmethod.jp/server-side/elasticsearch/released-elastic-stack-5-2-0/https://www.elastic.co/blog/strings-are-dead-long-live-strings などをみるとバージョン 5 以降で変更になったみたいです。

また、後述のクエリのために dateのindexもはりました。

app/models/concerns/event_searchable.rb
    # マッピング情報
    settings do
      mappings dynamic: 'false' do # 動的にマッピングを生成しない
        indexes :name, analyzer: 'kuromoji', type: 'text'
        indexes :content, analyzer: 'kuromoji', type: 'text'
        indexes :start_at, type: 'date'
        indexes :end_at, type: 'date'
      end
    end

検索用のクエリ書く

検索は app/forms/event_search.rb に書いていますので、それをelasticsearchを使った形に修正します。

search_paramsにはcontent_cont_all というransackに対応するようにkeyが割り振られていて、それを直接つかって検索していました。
elastcisearchではransackのdslは使わないので、そこに入っている検索キーワード(スペース区切り)を取り出して search_params[:content_cont_all] 渡しています。

ransackの content_cont_all に該当するような全てにマッチするクエリを作るため boolとmustをつかいました。
mustに配列を渡すと、配列内の条件を AND でつなげて検索します。

日付の指定にはrangeを使います。 元の検索式は、ransack の start_at_gteq: @start_dateを使ってます。これをelasticsearchのクエリで書くと `gte: 'now' となります(gteは greater than equal の略)。

レコードのソートは sortを使います。元の検索式では、 order(start_at: :asc) と書いています。これを elasticsearchのクエリで書くと sort: { start_at: { order: 'asc' } } となります。

件数の指定は、size を使います。 今回は最大50件にしたかったので size: 50 と書きました。

コントローラー

app/controllers/home_controller.rb
class HomeController < ApplicationController
  include Pagy::Backend
  def search
    @event_search = if params[:event_search].present?
                      EventSearch.new(event_search_params)
                    else
                      EventSearch.new
                    end
    @pagy, @events = pagy(@event_search.exec)
  end

  private

  def event_search_params
    params.require(:event_search).permit(:keyword)
  end
end

EventSearch クラスElasticsearch対応前

app/forms/event_search.rb
  def exec
    Event.search(search_params).result.order(start_at: :asc)
  end

EventSearch クラスElasticsearch対応後

app/forms/event_search.rb
  def exec
    query = { query: {
      bool: {
        must: [range: {
          end_at: {
            gte: 'now'
          }
        }] + search_params[:content_cont_all].map { |key| { match: { content: key } } }
      }
    },
              sort: { start_at: { order: 'asc' } },
              size: 50 }
    Event.search(query).records.all
  end

pagyのpaginationに対応させる

ページネーションはpagyでやっています。こちらに対応させます・・・が特にやることないです。
pagyは @pagy, @events = pagy(@event_search.exec) のように使います。pagyの引数はActiveRecord_Relationです。
elasticsearchの検索結果にたいして all メソッドを実行すればをActiveRecord_Relationが返りますのでそれだけでOKです。

app/forms/event_search.rb
Event.search(query).records.all

以上

11
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
19