RailsでのElasticsearchによる全文検索
Elasticsearchは、世の中で最も人気のある検索エンジンの1つです。それを愛し、制作に積極的に使用している多くの大企業の中には、Netflix、Medium、GitHubなどの巨人がいます。
Elasticsearchは非常に強力であり、主なユースケースは全文検索、リアルタイムログ、セキュリティ分析を特徴としています。
残念ながら、ElasticsearchはRailsコミュニティからあまり注目されていないため、この記事では、Elasticsearchの概念を読者に紹介し、RubyonRailsで使用する方法を示すという2つの目標を念頭に置いてこれを変更しようとしています。
ここでビルドするサンプルプロジェクトのソースコードを見つけることができます。コミット履歴は、この記事のセクションの順序にほぼ対応しています。
より広い観点から、Elasticsearchは検索エンジンです
- ApacheLuceneの上に構築されています;
- JSONドキュメントを保存して効果的にインデックス付けします。
- はオープンソースです;
- それと対話するためのRESTAPIのセットを提供します;
- デフォルトではセキュリティはありません(誰でもパブリックエンドポイントを介してクエリできます)。
- 水平方向にかなりうまくスケーリングします。
基本的な概念のいくつかを簡単に見てみましょう。
Elasticsearchを使用して、ドキュメントをインデックスに入れ、データをクエリします。
インデックス リレーショナルデータベースのテーブルに似ています。 書類を置くお店です (行)後で照会できます。
ドキュメント フィールドのコレクションです(リレーショナルデータベースの行に似ています)。
マッピング リレーショナルデータベースのスキーマ定義のようなものです。マッピングは明示的に定義することも、挿入時にElasticsearchによって推測することもできます。インデックスマッピングを事前に定義することをお勧めします。
それが終わったら、環境を設定しましょう。
MacOSにElasticsearchをインストールする最も簡単な方法は、brewを使用することです:
brew tap elastic/tap
brew install elastic/tap/elasticsearch-full
別の方法として、dockerを介して実行することもできます:
docker run \
-p 127.0.0.1:9200:9200 \
-p 127.0.0.1:9300:9300 \
-e "discovery.type=single-node" \
docker.elastic.co/elasticsearch/elasticsearch:7.16.2
その他のオプションについては、公式リファレンスを参照してください。
Elasticsearchは、デフォルトでポート9200でリクエストを受け入れます。簡単なcurlリクエストで実行されていることを確認できます(またはブラウザで開きます):
curl https://localhost:9200
API
Elasticsearchは、考えられるすべてのタイプのタスクに対して対話するための一連のRESTAPIを提供します。たとえば、JSONコンテンツタイプでPOSTリクエストを実行して、ドキュメントを作成するとします。
curl -X POST https://localhost:9200/my-index/_doc \
-H 'Content-Type: application/json' \
-d '{"title": "Banana Cake"}'
この場合、 my-index
はインデックスの名前です(インデックスが存在しない場合は、自動的に作成されます)。
_doc
はシステムルートです(すべてのシステムルートはアンダースコアで始まります)。
APIを操作する方法は複数あります。
-
curl
の使用 コマンドラインから(jqが便利な場合があります)。 - JSONをきれいに印刷するための拡張機能を使用して、ブラウザからGETクエリを実行します。
- Kibanaをインストールし、開発ツールコンソールを使用するのが私のお気に入りの方法です。
- 最後に、いくつかの優れたChrome拡張機能もあります。
この記事のために、どちらを選択するかは重要ではありません。とにかく、APIと直接対話することはありません。代わりに、内部でRESTAPIと通信するgemを使用します。
アイデアは、26K以上の曲の公開データセットを使用して歌詞アプリケーションを作成することです。各曲には、タイトル、アーティスト、ジャンル、テキストの歌詞フィールドがあります。全文検索にはElasticsearchを使用します。
簡単なRailsアプリケーションを作成することから始めましょう:
rails new songs_api --api -d postgresql
APIとしてのみ使用するため、-api
を提供します 使用するミドルウェアのセットを制限するフラグ。
アプリの足場を作りましょう:
bin/rails generate scaffold Song title:string artist:string genre:string lyrics:text
それでは、移行を実行してサーバーを起動しましょう。
bin/rails db:create db:migrate
bin/rails server
その後、GETエンドポイントが機能することを確認します。
curl https://localhost:3000/songs
これにより空の配列が返されますが、まだデータがないので不思議ではありません。
Elasticsearchの紹介
Elasticsearchをミックスに追加しましょう。そのためには、elasticsearch-modelgemが必要になります。これは、Railsモデルとうまく統合できる公式のElasticsearchgemです。
Gemfile
に以下を追加します :
gem 'elasticsearch-model'
デフォルトでは、ローカルホストのポート9200に接続します。これは私たちにぴったりですが、それを変更したい場合は、
でクライアントを初期化できます。Song.__elasticsearch__.client = Elasticsearch::Client.new host: 'myserver.com', port: 9876
次に、Elasticsearchでモデルをインデックスに登録できるようにするには、2つのことを行う必要があります。まず、マッピングを準備する必要があります(これは、基本的にElasticsearchにデータ構造を通知します)。次に、検索リクエストを作成する必要があります。私たちの宝石は両方を行うことができるので、それを使用する方法を見てみましょう。
Elastisearch関連のコードを別のモジュールに保持することは常に良い考えです。そのため、 app / models / concerns / searchable.rb
で懸念事項を作成しましょう。 追加
# app/models/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
mapping do
# mapping definition goes here
end
def self.search(query)
# build and run search
end
end
end
単なるスケルトンですが、ここで開梱するものがあります。
最初で最も重要なことは、 Elasticsearch ::Model
です。 、ESと対話するためのいくつかの機能を追加します。 Elasticsearch ::Model ::Callbacks
モジュールは、レコードを更新するときに、Elasticsearchのデータを自動的に更新することを保証します。 マッピング
ブロックは、Elasticsearchインデックスマッピングを配置する場所です。これは、Elasticsearchに格納されるフィールドと、それらが持つべきタイプを定義します。最後に、 search
があります Elasticsearchで曲の歌詞を実際に検索するために使用する方法。私たちが使用している宝石は、 search
を提供します Song.search( "genesis")
のような単純なクエリで使用できるメソッド 、ただし、クエリDSLを使用して構築されたより複雑な検索クエリで使用します(詳細は後で説明します)。
モデルクラスに懸念事項を含めることを忘れないでください:
# /app/models/song.rb
class Song < ApplicationRecord
include Searchable
end
Elasticsearchでは、マッピングはリレーショナルデータベースのスキーマ定義のようなものです。保存したいドキュメントの構造を説明します。通常のリレーショナルデータベースとは異なり、マッピングを事前に定義する必要はありません。Elasticsearchは、タイプを推測するために最善を尽くします。それでも、驚きはしたくないので、事前にマッピングを明示的に定義します。
マッピングは、 PUT / my-index / _mapping
を使用してRESTエンドポイントを介して更新できます GET / my-index / _mapping
を介して読み取ります 、ただし、 elasticsearch
gemはそれを抽象化するので、必要なのはマッピング
を提供することだけです。 ブロック:
# app/models/concerns/searchable.rb
mapping do
indexes :artist, type: :text
indexes :title, type: :text
indexes :lyrics, type: :text
indexes :genre, type: :keyword
end
artist
にインデックスを付けます 、 title
、および歌詞
テキストタイプを使用するフィールド。これは、全文検索で索引付けされる唯一のタイプです。 ジャンル
の場合 、キーワードタイプを使用します。これは、正確な値でフィルタリングされた理想的な検索です。
次に、 bin / rails console
を使用してRailsコンソールを実行します。 次に実行します
Song.__elasticsearch__.create_index!
これにより、Elasticsearchにインデックスが作成されます。 __ elasticsearch __
オブジェクトはElasticsearchの世界への門であり、Elasticsearchとやり取りするための便利なメソッドがたくさん詰まっています。
レコードを作成するたびに、Elasticsearchにデータが自動的に送信されます。そこで、歌詞を含むデータセットをダウンロードして、アプリにインポートします。まず、このリンクからダウンロードします( Creative Commons Attribution4.0Internationalライセンス
のデータセット )。このCSVファイルには26,000を超えるレコードが含まれており、以下のコードを使用してデータベースとElasticsearchにインポートします。
require 'csv'
class Song < ApplicationRecord
include Searchable
def self.import_csv!
filepath = "/path/to/your/file/tcc_ceds_music.csv"
res = CSV.parse(File.read(filepath), headers: true)
res.each_with_index do |s, ind|
Song.create!(
artist: s["artist_name"],
title: s["track_name"],
genre: s["genre"],
lyrics: s["lyrics"]
)
end
end
end
Railsコンソールを開き、 Song.import_csv!
を実行します (これには少し時間がかかります)。または、データを一括でインポートすることもできます。これははるかに高速ですが、この場合は、PostgreSQLデータベースとElasticsearchにレコードを作成する必要があります。
インポートが完了すると、検索できる歌詞がたくさんあります。
elasticsearch-model
gemはsearch
を追加します すべてのインデックス付きフィールドを検索できるようにするメソッド。検索可能な懸念事項に使用しましょう:
# app/models/concerns/searchable.rb
# ...
def self.search(query)
self.__elasticsearch__.search(query)
end
# ...
Railsコンソールを開き、 res =Song.search('genesis')
を実行します。 。応答オブジェクトには、リクエストにかかった時間、使用されたノードなど、多くのメタ情報が含まれています。 res.response ["hits"] ["hits"]
> 。
コントローラのindex
を変更しましょう 代わりにESを照会する方法。
# app/controllers/songs_controller.rb
def index
query = params["query"] || ""
res = Song.search(query)
render json: res.response["hits"]["hits"]
end
これで、ブラウザにロードするか、curl http:// localhost:3000 / songs?query =genesis
を使用して試すことができます。 。応答は次のようになります:
[
{
"_index": "songs",
"_type": "_doc",
"_id": "22676",
"_score": 12.540506,
"_source": {
"id": 22676,
"title": "genesis",
"artist": "grimes",
"genre": "pop",
"lyrics": "heart know heart ...",
"created_at": "...",
"updated_at": "..."
}
},
...
]
ご覧のとおり、実際のデータは _source
の下に返されます。 キー、他のフィールドはメタデータであり、その中で最も重要なのは _score
です。 ドキュメントが特定の検索にどのように関連しているかを示します。すぐにわかりますが、最初にクエリの作成方法を学びましょう。
ElasticsearchクエリDSLは、複雑なクエリを構築する方法を提供し、rubyコードからも使用できます。たとえば、検索方法を変更して、アーティストフィールドのみを検索してみましょう。
# app/models/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
included do
# ...
def self.search(query)
params = {
query: {
match: {
artist: query,
},
},
}
self.__elasticsearch__.search(params)
end
end
end
query-match構文を使用すると、特定のフィールド(この場合はアーティスト)のみを検索できます。ここで、「genesis」を使用して曲を再度クエリすると、 http:// localhost:3000 / songs?query =genesis
を読み込んで試してみてください。 )、バンド「Genesis」の曲のみを取得し、タイトルに「genesis」が含まれる曲は取得しません。多くの場合、複数のフィールドをクエリする場合は、マルチマッチクエリを使用できます。
# app/models/concerns/searchable.rb
def self.search(query)
params = {
query: {
multi_match: {
query: query,
fields: [ :title, :artist, :lyrics ]
},
},
}
self.__elasticsearch__.search(params)
end
たとえば、ロックソングの中だけを検索したい場合はどうなりますか?次に、ジャンルでフィルタリングする必要があります!これにより、検索が少し複雑になりますが、心配しないでください。すべてを段階的に説明します。
def self.search(query, genre = nil)
params = {
query: {
bool: {
must: [
{
multi_match: {
query: query,
fields: [ :title, :artist, :lyrics ]
}
},
],
filter: [
{
term: { genre: genre }
}
]
}
}
}
self.__elasticsearch__.search(params)
end
最初の新しいキーワードはboolです。これは、複数のクエリを1つに結合する方法にすぎません。この例では、 must
を組み合わせています およびfilter
。最初のもの( must
)スコアに貢献し、以前に使用したものと同じクエリが含まれています。 2つ目( filter
)はスコアに寄与しません。それは単にそれが言うことをします:クエリに一致しないドキュメントを除外します。レコードをジャンルでフィルタリングしたいので、クエリという用語を使用します。
filter-term
に注意することが重要です 組み合わせは、全文検索とは何の関係もありません。これは、 WHERE
と同じように、正確な値による通常のフィルターです。 句はSQLで機能します( WHERE genre ='rock'
)。 term
の使い方を知っておくとよいでしょう フィルタリングしますが、ここでは必要ありません。
検索結果は_score
の順に並べられています これは、アイテムが特定の検索にどのように関連しているかを示しています。スコアが高いほど、ドキュメントの関連性が高くなります。 genesis
という単語を検索したときにお気づきかもしれません。 、最初にポップアップした結果はグライムスの曲でしたが、実際にはジェネシスバンドにもっと興味がありました。では、アーティストの分野にもっと注意を払うようにスコアリングメカニズムを変更することはできますか?はい、できますが、そのためには、最初にクエリを微調整する必要があります:
def self.search(query)
params = {
query: {
bool: {
should: [
{ match: { title: query }},
{ match: { artist: query }},
{ match: { lyrics: query }},
],
}
},
}
self.__elasticsearch__.search(params)
end
このクエリは、boolキーワードを使用していることを除いて、基本的に前のクエリと同じです。これは、複数のクエリを1つに結合する方法にすぎません。 should
を使用します 、3つのクエリが個別に含まれています(フィールドごとに1つ)。これらは基本的に論理ORを使用して結合されます。 must
を使用する場合 代わりに、論理積を使用して結合されます。フィールドごとに個別の一致が必要なのはなぜですか?これは、特定のクエリのスコアを乗算する係数であるブーストプロパティを指定できるようになったためです。
def self.search(query)
params = {
query: {
bool: {
should: [
{ match: { title: query }},
{ match: { artist: { query: query, boost: 5 } }},
{ match: { lyrics: query }},
],
}
},
}
self.__elasticsearch__.search(params)
end
他の条件が同じであれば、クエリがアーティストと一致する場合、スコアは5倍高くなります。 genesis
をお試しください http:// localhost:3000 / songs?query =genesis
を使用して、もう一度クエリを実行します 、そしてあなたはジェネシスバンドの曲が最初に来るのを見るでしょう。甘い!
Elasticsearchのもう1つの便利な機能は、ドキュメント内の一致を強調表示できることです。これにより、ユーザーは特定の結果が検索に表示された理由をよりよく理解できます。
HTMLには、そのための特別なHTMLタグがあり、Elasticsearchはそれを自動的に追加できます。
searchable.rb
を開いてみましょう もう一度懸念し、新しいキーワードを追加します:
def self.search(query)
params = {
query: {
bool: {
should: [
{ match: { title: query }},
{ match: { artist: { query: query, boost: 5 } }},
{ match: { lyrics: query }},
],
}
},
highlight: { fields: { title: {}, artist: {}, lyrics: {} } }
}
self.__elasticsearch__.search(params)
end
新しいハイライト
fieldは、強調表示するフィールドを指定します。それらすべてを選択します。ここで、 http:// localhost:3000 / query =genesis
をロードすると 、 em
でラップされた一致するフレーズを持つドキュメントフィールドを含む「highlight」と呼ばれる新しいフィールドが表示されます。 タグ。
ハイライトの詳細については、公式ガイドを参照してください。
了解しました。誤ってbenesis
と書いた場合はどうでしょうか。 genesis
の代わりに ?これは結果を返しませんが、Elasticsearchにうるさくなく、あいまい検索を許可するように指示できるため、 genesis
が表示されます。 結果も。
これがその方法です。アーティストクエリを{match:{artist:{query:query、boost:5}}}
から変更するだけです。 to {match:{artist:{query:query、boost:5、fuzziness: "AUTO"}}}
。正確なあいまいさのメカニズムを構成できます。詳細については、公式ドキュメントを参照してください。
次はどこへ?
この記事で、Elasticsearchが強力なツールであり、重要な検索を実装する必要がある場合に使用でき、使用する必要があることを確信していただければ幸いです。詳細を確認する準備ができている場合は、次のリンクをご覧ください。
- Elasticsearchの公式リファレンス
- Rubyの宝石
- レールの宝石
- 実践的な知識が満載のとても素敵な本
- オートコンプリートの構築
- サーチキック
- 歯ごたえ
-
RailsでのTailwindCSSの使用
CSSは魔法のようですが、時間がかかります。美しく、機能的で、アクセスしやすいサイトを使用するのは楽しいことですが、独自のCSSを作成するのは大変です。 Bootstrapなどの多くのCSSライブラリは近年爆発的に増加しており、Tailwindは2021年にパックをリードしています。 RailsにはTailwindが付属していませんが、この記事では、TailwindCSSを新しいRubyon Railsプロジェクトに追加する方法を説明します。これにより、設計の実装にかかる時間を節約できます。また、Tailwindのユーティリティクラスを使用した設計のウォークスルーも行います。このチュートリア
-
Rails5でのAngularの使用
あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた